diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 1d1a6eec16..c9cbf4e7f5 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -75,7 +75,7 @@ { "label": "Build Immich CLI", "type": "shell", - "command": "pnpm --filter cli build:dev" + "command": "pnpm --filter @immich/cli build:dev" } ] } diff --git a/.devcontainer/server/container-compose-overrides.yml b/.devcontainer/server/container-compose-overrides.yml index 5c312efd07..c5db2a6b32 100644 --- a/.devcontainer/server/container-compose-overrides.yml +++ b/.devcontainer/server/container-compose-overrides.yml @@ -15,8 +15,8 @@ services: volumes: - ${UPLOAD_LOCATION:-upload-devcontainer-volume}${UPLOAD_LOCATION:+/photos}:/data - /etc/localtime:/etc/localtime:ro - - pnpm_store_server:/buildcache/pnpm-store - - ../plugins:/build/corePlugin + - build_cache:/buildcache + - ../packages/plugin-core:/build/plugins/immich-plugin-core immich-web: env_file: !reset [] immich-machine-learning: diff --git a/.devcontainer/server/container-start-frontend.sh b/.devcontainer/server/container-start-frontend.sh index 9a0d617d41..e7f1dcc1d0 100755 --- a/.devcontainer/server/container-start-frontend.sh +++ b/.devcontainer/server/container-start-frontend.sh @@ -8,6 +8,8 @@ log "Preparing Immich Web Frontend" log "" run_cmd pnpm --filter @immich/sdk install run_cmd pnpm --filter @immich/sdk build +run_cmd pnpm --filter @immich/plugin-sdk install +run_cmd pnpm --filter @immich/plugin-sdk build run_cmd pnpm --filter immich-web install log "Starting Immich Web Frontend" diff --git a/.dockerignore b/.dockerignore index f7efb5c56e..4d8e2160c2 100644 --- a/.dockerignore +++ b/.dockerignore @@ -30,9 +30,7 @@ machine-learning/ misc/ mobile/ -open-api/typescript-sdk/build/ -!open-api/typescript-sdk/package.json -!open-api/typescript-sdk/package-lock.json +packages/sdk/build/ server/upload/ server/src/queries diff --git a/.gitattributes b/.gitattributes index e1225939b1..f1d1336935 100644 --- a/.gitattributes +++ b/.gitattributes @@ -24,7 +24,7 @@ mobile/lib/infrastructure/repositories/db.repository.steps.dart linguist-generat mobile/test/drift/main/generated/** -diff -merge mobile/test/drift/main/generated/** linguist-generated=true -open-api/typescript-sdk/fetch-client.ts -diff -merge -open-api/typescript-sdk/fetch-client.ts linguist-generated=true +packages/sdk/fetch-client.ts -diff -merge +packages/sdk/fetch-client.ts linguist-generated=true *.sh text eol=lf diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index acbb7c785b..0000000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1 +0,0 @@ -custom: ['https://buy.immich.app', 'https://immich.store'] diff --git a/.github/labeler.yml b/.github/labeler.yml index d0e4a3097b..824bd5c775 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -1,7 +1,7 @@ cli: - changed-files: - any-glob-to-any-file: - - cli/src/** + - packages/cli/src/** documentation: - changed-files: diff --git a/.github/workflows/build-mobile.yml b/.github/workflows/build-mobile.yml index 72e8b10aeb..1a4243ecae 100644 --- a/.github/workflows/build-mobile.yml +++ b/.github/workflows/build-mobile.yml @@ -51,14 +51,14 @@ jobs: should_run: ${{ steps.check.outputs.should_run }} steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 + uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1 with: - app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - name: Check what should run id: check - uses: immich-app/devtools/actions/pre-job@f50e3b600b6ac1763ddb8f3dfc69093512b967a1 # pre-job-action-v2.0.3 + uses: immich-app/devtools/actions/pre-job@91f342bb4477c4bc10c576ae739da875d85aa164 # pre-job-action-v2.0.4 with: github-token: ${{ steps.token.outputs.token }} filters: | @@ -73,24 +73,31 @@ jobs: needs: pre-job permissions: contents: read - # Skip when PR from a fork - if: ${{ !github.event.pull_request.head.repo.fork && github.actor != 'dependabot[bot]' && fromJSON(needs.pre-job.outputs.should_run).mobile == true }} + pull-requests: write + if: ${{ github.actor != 'dependabot[bot]' && fromJSON(needs.pre-job.outputs.should_run).mobile == true }} runs-on: mich steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 + uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1 with: - app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: - ref: ${{ inputs.ref || github.sha }} + ref: ${{ inputs.ref }} persist-credentials: false token: ${{ steps.token.outputs.token }} + - name: Setup Mise + uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2 + with: + github_token: ${{ steps.token.outputs.token }} + working_directory: ./mobile + - name: Create the Keystore + if: ${{ !github.event.pull_request.head.repo.fork }} env: KEY_JKS: ${{ secrets.KEY_JKS }} working-directory: ./mobile @@ -110,16 +117,8 @@ jobs: ~/.gradle/wrapper ~/.android/sdk mobile/android/.gradle - mobile/.dart_tool key: build-mobile-gradle-${{ runner.os }}-main - - name: Setup Flutter SDK - uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2.23.0 - with: - channel: 'stable' - flutter-version-file: ./mobile/pubspec.yaml - cache: true - - name: Setup Android SDK uses: android-actions/setup-android@40fd30fb8d7440372e1316f5d1809ec01dcd3699 # v4.0.1 with: @@ -130,11 +129,10 @@ jobs: run: flutter pub get - name: Generate translation file - run: dart run easy_localization:generate -S ../i18n && dart run bin/generate_keys.dart - working-directory: ./mobile + run: mise //mobile:codegen:translation - name: Generate platform APIs - run: make pigeon + run: mise //mobile:codegen:pigeon working-directory: ./mobile - name: Build Android App Bundle @@ -144,20 +142,43 @@ jobs: ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} ANDROID_STORE_PASSWORD: ${{ secrets.ANDROID_STORE_PASSWORD }} IS_MAIN: ${{ github.ref == 'refs/heads/main' }} + PR_NUMBER: ${{ github.event.pull_request.number }} run: | if [[ $IS_MAIN == 'true' ]]; then flutter build apk --release flutter build apk --release --split-per-abi --target-platform android-arm,android-arm64,android-x64 else - flutter build apk --debug --split-per-abi --target-platform android-arm64 + flutter build apk --release fi - name: Publish Android Artifact + id: upload-apk uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: release-apk-signed path: mobile/build/app/outputs/flutter-apk/*.apk + - name: Comment APK download link on PR + if: ${{ github.event_name == 'pull_request' && !github.event.pull_request.head.repo.fork }} + uses: immich-app/devtools/actions/sticky-comment@0135acd12ad9f3369b94a2aa3c0ae8c835a4e926 # sticky-comment-action-v1.0.0 + env: + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + APK_URL: ${{ steps.upload-apk.outputs.artifact-url }} + with: + id: mobile-android-apk + token: ${{ steps.token.outputs.token }} + body: | + ๐Ÿ“ฑ **Android release APK (universal)** โ€” `${{ env.HEAD_SHA }}` + + Download: ${{ env.APK_URL }} + +
+ QR code + QR code +
+ + Installs as a separate app (applicationId `app.alextran.immich.pr${{ github.event.pull_request.number }}`), so it coexists with the Play Store version and any other PR builds. + - name: Save Gradle Cache id: cache-gradle-save uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 @@ -168,7 +189,6 @@ jobs: ~/.gradle/wrapper ~/.android/sdk mobile/android/.gradle - mobile/.dart_tool key: ${{ steps.cache-gradle-restore.outputs.cache-primary-key }} build-sign-ios: @@ -181,6 +201,12 @@ jobs: runs-on: macos-15 steps: + - id: token + uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1 + with: + client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} + private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} + - name: Select Xcode 26 run: sudo xcode-select -s /Applications/Xcode_26.2.app/Contents/Developer @@ -190,27 +216,28 @@ jobs: ref: ${{ inputs.ref || github.sha }} persist-credentials: false - - name: Setup Flutter SDK - uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2.23.0 + - name: Setup Mise + uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2 with: - channel: 'stable' - flutter-version-file: ./mobile/pubspec.yaml - cache: true + github_token: ${{ steps.token.outputs.token }} + working_directory: ./mobile - name: Install Flutter dependencies working-directory: ./mobile run: flutter pub get - name: Generate translation files - run: dart run easy_localization:generate -S ../i18n && dart run bin/generate_keys.dart - working-directory: ./mobile + run: mise //mobile:codegen:translation - name: Generate platform APIs - run: make pigeon + run: mise //mobile:codegen:pigeon + + - name: Resolve iOS Swift Packages working-directory: ./mobile + run: flutter build ios --config-only --no-codesign - name: Setup Ruby - uses: ruby/setup-ruby@7372622e62b60b3cb750dcd2b9e32c247ffec26a # v1.302.0 + uses: ruby/setup-ruby@afeafc3d1ab54a631816aba4c914a0081c12ff2f # v1.310.0 with: ruby-version: '3.3' bundler-cache: true @@ -267,7 +294,6 @@ jobs: APP_STORE_CONNECT_API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }} APP_STORE_CONNECT_API_KEY_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ISSUER_ID }} ENVIRONMENT: ${{ inputs.environment || 'development' }} - BUNDLE_ID_SUFFIX: ${{ inputs.environment == 'production' && '' || 'development' }} GITHUB_REF: ${{ github.ref }} FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT: 120 FASTLANE_XCODEBUILD_SETTINGS_RETRIES: 6 diff --git a/.github/workflows/cache-cleanup.yml b/.github/workflows/cache-cleanup.yml index e093cf9bf0..aa801d851a 100644 --- a/.github/workflows/cache-cleanup.yml +++ b/.github/workflows/cache-cleanup.yml @@ -19,9 +19,9 @@ jobs: actions: write steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 + uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1 with: - app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - name: Check out code diff --git a/.github/workflows/check-openapi.yml b/.github/workflows/check-openapi.yml index f2d03f7400..1346d05112 100644 --- a/.github/workflows/check-openapi.yml +++ b/.github/workflows/check-openapi.yml @@ -4,6 +4,7 @@ on: pull_request: paths: - 'open-api/**' + - 'mobile/lib/utils/openapi_patching.dart' - '.github/workflows/check-openapi.yml' concurrency: @@ -24,8 +25,41 @@ jobs: persist-credentials: false - name: Check for breaking API changes - uses: oasdiff/oasdiff-action/breaking@f8cb9308b42121e793f835bd14c0b8090420430c # v0.0.39 + uses: oasdiff/oasdiff-action/breaking@50e6a3413e5aa9c3ae4d8393c34745be44288b46 # v0.0.48 with: base: https://raw.githubusercontent.com/${{ github.repository }}/main/open-api/immich-openapi-specs.json revision: open-api/immich-openapi-specs.json fail-on: ERR + + check-mobile-patches: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Setup Mise + uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2 + with: + github_token: ${{ github.token }} + working_directory: ./mobile + + - name: Get packages + working-directory: ./mobile + run: flutter pub get + + - name: Fetch base spec from main + run: | + curl -fsSL \ + "https://raw.githubusercontent.com/${{ github.repository }}/main/open-api/immich-openapi-specs.json" \ + -o /tmp/base-spec.json + + - name: Check newly-required fields have a backward-compat patch + working-directory: ./mobile + env: + OPENAPI_BASE_SPEC: /tmp/base-spec.json + OPENAPI_REVISION_SPEC: ../open-api/immich-openapi-specs.json + run: flutter test test/openapi_patches_coverage.dart diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index 2a334af89d..3a17dbb579 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -3,11 +3,11 @@ on: push: branches: [main] paths: - - 'cli/**' + - 'packages/cli/**' - '.github/workflows/cli.yml' pull_request: paths: - - 'cli/**' + - 'packages/cli/**' - '.github/workflows/cli.yml' release: types: [published] @@ -28,38 +28,30 @@ jobs: packages: write defaults: run: - working-directory: ./cli + working-directory: ./packages/cli steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 + uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1 with: - app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Checkout code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false token: ${{ steps.token.outputs.token }} - - name: Setup pnpm - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 - - - name: Setup Node - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + - name: Setup Mise + uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2 with: - node-version-file: './cli/.nvmrc' - registry-url: 'https://registry.npmjs.org' - cache: 'pnpm' - cache-dependency-path: '**/pnpm-lock.yaml' + github_token: ${{ steps.token.outputs.token }} - - name: Setup typescript-sdk - run: pnpm install && pnpm run build - working-directory: ./open-api/typescript-sdk - - - run: pnpm install --frozen-lockfile - - run: pnpm build - - run: pnpm publish --provenance --no-git-checks + - name: Publish if: ${{ github.event_name == 'release' }} + env: + NPM_TAG: ${{ github.event.release.prerelease && 'rc' || 'latest' }} + run: mise run ci-publish -- --tag "$NPM_TAG" docker: name: Docker @@ -71,9 +63,9 @@ jobs: steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 + uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1 with: - app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - name: Checkout @@ -83,13 +75,13 @@ jobs: token: ${{ steps.token.outputs.token }} - name: Set up QEMU - uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0 + uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 + uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 - name: Login to GitHub Container Registry - uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 + uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 if: ${{ !github.event.pull_request.head.repo.fork }} with: registry: ghcr.io @@ -99,12 +91,12 @@ jobs: - name: Get package version id: package-version run: | - version=$(jq -r '.version' cli/package.json) + version=$(jq -r '.version' packages/cli/package.json) echo "version=$version" >> "$GITHUB_OUTPUT" - name: Generate docker image tags id: metadata - uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0 + uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0 with: flavor: | latest=false @@ -112,12 +104,12 @@ jobs: name=ghcr.io/${{ github.repository_owner }}/immich-cli tags: | type=raw,value=${{ steps.package-version.outputs.version }},enable=${{ github.event_name == 'release' }} - type=raw,value=latest,enable=${{ github.event_name == 'release' }} + type=raw,value=latest,enable=${{ github.event_name == 'release' && !github.event.release.prerelease }} - name: Build and push image - uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 + uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0 with: - file: cli/Dockerfile + file: packages/cli/Dockerfile platforms: linux/amd64,linux/arm64 push: ${{ github.event_name == 'release' }} cache-from: type=gha diff --git a/.github/workflows/close-duplicates.yml b/.github/workflows/close-duplicates.yml index 839e5b3ceb..590256058d 100644 --- a/.github/workflows/close-duplicates.yml +++ b/.github/workflows/close-duplicates.yml @@ -35,7 +35,7 @@ jobs: needs: [get_body, should_run] if: ${{ needs.should_run.outputs.should_run == 'true' }} container: - image: ghcr.io/immich-app/mdq:main@sha256:557cca601891b8b7d78b940071d35aaf7aaeb9b327d19b22cf282118edbc5272 + image: ghcr.io/immich-app/mdq:main@sha256:e73f60195b39748c4876f23e3e6cd22a68a9754acec8aef1fd6979fd52cd2c9f outputs: checked: ${{ steps.get_checkbox.outputs.checked }} steps: diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index f2d99ae1e8..d10fd72335 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -44,9 +44,9 @@ jobs: steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 + uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1 with: - app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - name: Checkout repository @@ -57,7 +57,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2 + uses: github/codeql-action/init@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -70,7 +70,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2 + uses: github/codeql-action/autobuild@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0 # โ„น๏ธ Command-line programs to run using the OS shell. # ๐Ÿ“š See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun @@ -83,6 +83,6 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2 + uses: github/codeql-action/analyze@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0 with: category: '/language:${{matrix.language}}' diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 84509103be..133950fb0a 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -23,14 +23,14 @@ jobs: should_run: ${{ steps.check.outputs.should_run }} steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 + uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1 with: - app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - name: Check what should run id: check - uses: immich-app/devtools/actions/pre-job@f50e3b600b6ac1763ddb8f3dfc69093512b967a1 # pre-job-action-v2.0.3 + uses: immich-app/devtools/actions/pre-job@91f342bb4477c4bc10c576ae739da875d85aa164 # pre-job-action-v2.0.4 with: github-token: ${{ steps.token.outputs.token }} filters: | @@ -60,7 +60,7 @@ jobs: suffix: ['', '-cuda', '-rocm', '-openvino', '-armnn', '-rknn'] steps: - name: Login to GitHub Container Registry - uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 + uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 with: registry: ghcr.io username: ${{ github.repository_owner }} @@ -90,7 +90,7 @@ jobs: suffix: [''] steps: - name: Login to GitHub Container Registry - uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 + uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 with: registry: ghcr.io username: ${{ github.repository_owner }} @@ -132,7 +132,7 @@ jobs: suffixes: '-rocm' platforms: linux/amd64 runner-mapping: '{"linux/amd64": "pokedex-large"}' - uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@61a0fc2b41524edcc7c9fffb8bb178e6b0ccf21d # multi-runner-build-workflow-v2.3.0 + uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@db54dcf16fbb12c43479a23749ceea0ad1b4a704 # multi-runner-build-workflow-v3.0.0 permissions: contents: read actions: read @@ -147,7 +147,7 @@ jobs: platforms: ${{ matrix.platforms }} runner-mapping: ${{ matrix.runner-mapping }} suffixes: ${{ matrix.suffixes }} - dockerhub-push: ${{ github.event_name == 'release' }} + dockerhub-push: ${{ github.event_name == 'release' && !github.event.release.prerelease }} build-args: | DEVICE=${{ matrix.device }} @@ -155,7 +155,7 @@ jobs: name: Build and Push Server needs: pre-job if: ${{ fromJSON(needs.pre-job.outputs.should_run).server == true }} - uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@61a0fc2b41524edcc7c9fffb8bb178e6b0ccf21d # multi-runner-build-workflow-v2.3.0 + uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@db54dcf16fbb12c43479a23749ceea0ad1b4a704 # multi-runner-build-workflow-v3.0.0 permissions: contents: read actions: read @@ -167,7 +167,7 @@ jobs: image: immich-server context: . dockerfile: server/Dockerfile - dockerhub-push: ${{ github.event_name == 'release' }} + dockerhub-push: ${{ github.event_name == 'release' && !github.event.release.prerelease }} build-args: | DEVICE=cpu @@ -178,7 +178,7 @@ jobs: runs-on: ubuntu-latest if: always() steps: - - uses: immich-app/devtools/actions/success-check@53bb77345ee9f953f93bd6fd9980f07a2f24965e # success-check-action-v0.0.5 + - uses: immich-app/devtools/actions/success-check@81113db03f6d743efee81e0058c0b43f6cd6f36d # success-check-action-v0.0.6 with: needs: ${{ toJSON(needs) }} @@ -189,6 +189,6 @@ jobs: runs-on: ubuntu-latest if: always() steps: - - uses: immich-app/devtools/actions/success-check@53bb77345ee9f953f93bd6fd9980f07a2f24965e # success-check-action-v0.0.5 + - uses: immich-app/devtools/actions/success-check@81113db03f6d743efee81e0058c0b43f6cd6f36d # success-check-action-v0.0.6 with: needs: ${{ toJSON(needs) }} diff --git a/.github/workflows/docs-build.yml b/.github/workflows/docs-build.yml index 0ccebfb363..70ea4cb9e4 100644 --- a/.github/workflows/docs-build.yml +++ b/.github/workflows/docs-build.yml @@ -21,14 +21,14 @@ jobs: should_run: ${{ steps.check.outputs.should_run }} steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 + uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1 with: - app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - name: Check what should run id: check - uses: immich-app/devtools/actions/pre-job@f50e3b600b6ac1763ddb8f3dfc69093512b967a1 # pre-job-action-v2.0.3 + uses: immich-app/devtools/actions/pre-job@91f342bb4477c4bc10c576ae739da875d85aa164 # pre-job-action-v2.0.4 with: github-token: ${{ steps.token.outputs.token }} filters: | @@ -54,9 +54,9 @@ jobs: steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 + uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1 with: - app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - name: Checkout code @@ -64,17 +64,11 @@ jobs: with: persist-credentials: false token: ${{ steps.token.outputs.token }} - fetch-depth: 0 - - name: Setup pnpm - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 - - - name: Setup Node - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + - name: Setup Mise + uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2 with: - node-version-file: './docs/.nvmrc' - cache: 'pnpm' - cache-dependency-path: '**/pnpm-lock.yaml' + github_token: ${{ steps.token.outputs.token }} - name: Run install run: pnpm install diff --git a/.github/workflows/docs-deploy.yml b/.github/workflows/docs-deploy.yml index 57ea2d41d4..dd6ba4256e 100644 --- a/.github/workflows/docs-deploy.yml +++ b/.github/workflows/docs-deploy.yml @@ -20,9 +20,9 @@ jobs: artifact: ${{ steps.get-artifact.outputs.result }} steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 + uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1 with: - app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - if: ${{ github.event.workflow_run.conclusion != 'success' }} @@ -98,9 +98,16 @@ jobs: shouldDeploy: true }; } else if (eventType == "release") { + const tag = context.payload.workflow_run.head_branch; + const { data: release } = await github.rest.repos.getReleaseByTag({ + owner: context.repo.owner, + repo: context.repo.repo, + tag, + }); parameters = { event: "release", - name: context.payload.workflow_run.head_branch, + name: tag, + prerelease: release.prerelease, shouldDeploy: !isFork }; } @@ -119,9 +126,9 @@ jobs: if: ${{ fromJson(needs.checks.outputs.artifact).found && fromJson(needs.checks.outputs.parameters).shouldDeploy }} steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 + uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1 with: - app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - name: Checkout code @@ -131,7 +138,9 @@ jobs: token: ${{ steps.token.outputs.token }} - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@035e80a7d4355d5f087ffb95db9e4a0944c04e56 # use-mise-action-v1.1.3 + uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2 + with: + github_token: ${{ steps.token.outputs.token }} - name: Load parameters id: parameters @@ -144,6 +153,7 @@ jobs: const parameters = JSON.parse(process.env.PARAM_JSON); core.setOutput("event", parameters.event); core.setOutput("name", parameters.name); + core.setOutput("prerelease", parameters.prerelease); core.setOutput("shouldDeploy", parameters.shouldDeploy); - name: Download artifact @@ -201,7 +211,7 @@ jobs: run: mise run //docs:deploy - name: Deploy Docs Release Domain - if: ${{ steps.parameters.outputs.event == 'release' }} + if: ${{ steps.parameters.outputs.event == 'release' && steps.parameters.outputs.prerelease != 'true' }} env: TF_VAR_prefix_name: ${{ steps.parameters.outputs.name}} CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} @@ -211,12 +221,11 @@ jobs: run: 'mise run //deployment:tf apply' - name: Comment - uses: actions-cool/maintain-one-comment@909842216bc8e8658364c572ec52100f4c2cc50a # v3.3.0 + uses: immich-app/devtools/actions/sticky-comment@0135acd12ad9f3369b94a2aa3c0ae8c835a4e926 # sticky-comment-action-v1.0.0 if: ${{ steps.parameters.outputs.event == 'pr' }} with: + id: docs-pr-url token: ${{ steps.token.outputs.token }} number: ${{ fromJson(needs.checks.outputs.parameters).pr_number }} body: | ๐Ÿ“– Documentation deployed to [${{ steps.docs-output.outputs.subdomain }}](https://${{ steps.docs-output.outputs.subdomain }}) - emojis: 'rocket' - body-include: '' diff --git a/.github/workflows/docs-destroy.yml b/.github/workflows/docs-destroy.yml index 0e9c37a66c..19554b5d20 100644 --- a/.github/workflows/docs-destroy.yml +++ b/.github/workflows/docs-destroy.yml @@ -17,9 +17,9 @@ jobs: pull-requests: write steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 + uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1 with: - app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - name: Checkout code @@ -29,7 +29,9 @@ jobs: token: ${{ steps.token.outputs.token }} - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@035e80a7d4355d5f087ffb95db9e4a0944c04e56 # use-mise-action-v1.1.3 + uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2 + with: + github_token: ${{ steps.token.outputs.token }} - name: Destroy Docs Subdomain env: @@ -42,9 +44,8 @@ jobs: run: 'mise run //deployment:tf destroy -- -refresh=false' - name: Comment - uses: actions-cool/maintain-one-comment@909842216bc8e8658364c572ec52100f4c2cc50a # v3.3.0 + uses: immich-app/devtools/actions/sticky-comment@0135acd12ad9f3369b94a2aa3c0ae8c835a4e926 # sticky-comment-action-v1.0.0 with: + id: docs-pr-url token: ${{ steps.token.outputs.token }} - number: ${{ github.event.number }} delete: true - body-include: '' diff --git a/.github/workflows/fix-format.yml b/.github/workflows/fix-format.yml index 59cbb28fa8..6e48e89fd7 100644 --- a/.github/workflows/fix-format.yml +++ b/.github/workflows/fix-format.yml @@ -14,29 +14,23 @@ jobs: contents: write pull-requests: write steps: - - name: Generate a token - id: generate-token - uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1 + - id: token + uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1 with: - app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - - name: 'Checkout' + - name: Checkout code uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: ${{ github.event.pull_request.head.ref }} - token: ${{ steps.generate-token.outputs.token }} persist-credentials: true + token: ${{ steps.token.outputs.token }} - - name: Setup pnpm - uses: pnpm/action-setup@08c4be7e2e672a47d11bd04269e27e5f3e8529cb # v6.0.0 - - - name: Setup Node - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + - name: Setup Mise + uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2 with: - node-version-file: './server/.nvmrc' - cache: 'pnpm' - cache-dependency-path: '**/pnpm-lock.yaml' + github_token: ${{ steps.token.outputs.token }} - name: Fix formatting run: pnpm --recursive install && pnpm run --recursive --if-present --parallel format:fix diff --git a/.github/workflows/merge-translations.yml b/.github/workflows/merge-translations.yml index 08d3192f8b..ff00a16b6e 100644 --- a/.github/workflows/merge-translations.yml +++ b/.github/workflows/merge-translations.yml @@ -4,7 +4,7 @@ on: workflow_dispatch: workflow_call: secrets: - PUSH_O_MATIC_APP_ID: + PUSH_O_MATIC_APP_CLIENT_ID: required: true PUSH_O_MATIC_APP_KEY: required: true @@ -31,9 +31,9 @@ jobs: - name: Generate a token id: generate_token if: ${{ inputs.skip != true }} - uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1 + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 with: - app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - name: Find translation PR diff --git a/.github/workflows/org-zizmor.yml b/.github/workflows/org-zizmor.yml index 8510fd85b4..050e69b496 100644 --- a/.github/workflows/org-zizmor.yml +++ b/.github/workflows/org-zizmor.yml @@ -13,3 +13,4 @@ jobs: actions: read contents: read security-events: write + secrets: inherit diff --git a/.github/workflows/pr-label-validation.yml b/.github/workflows/pr-label-validation.yml index 416e40df0d..d99ce46d29 100644 --- a/.github/workflows/pr-label-validation.yml +++ b/.github/workflows/pr-label-validation.yml @@ -14,9 +14,9 @@ jobs: pull-requests: write steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 + uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1 with: - app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - name: Require PR to have a changelog label diff --git a/.github/workflows/pr-labeler.yml b/.github/workflows/pr-labeler.yml index 75ee750e9f..3d1646a88d 100644 --- a/.github/workflows/pr-labeler.yml +++ b/.github/workflows/pr-labeler.yml @@ -12,11 +12,11 @@ jobs: runs-on: ubuntu-latest steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 + uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1 with: - app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - - uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6.0.1 + - uses: actions/labeler@f27b608878404679385c85cfa523b85ccb86e213 # v6.1.0 with: repo-token: ${{ steps.token.outputs.token }} diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml index 2aa028b22e..6a7c2c9cf4 100644 --- a/.github/workflows/prepare-release.yml +++ b/.github/workflows/prepare-release.yml @@ -36,7 +36,7 @@ jobs: permissions: pull-requests: write secrets: - PUSH_O_MATIC_APP_ID: ${{ secrets.PUSH_O_MATIC_APP_ID }} + PUSH_O_MATIC_APP_CLIENT_ID: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} PUSH_O_MATIC_APP_KEY: ${{ secrets.PUSH_O_MATIC_APP_KEY }} WEBLATE_TOKEN: ${{ secrets.WEBLATE_TOKEN }} @@ -48,32 +48,27 @@ jobs: version: ${{ steps.output.outputs.version }} permissions: {} # No job-level permissions are needed because it uses the app-token steps: - - name: Generate a token - id: generate-token - uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1 + - id: token + uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1 with: - app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - - name: Checkout + - name: Checkout code uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: - token: ${{ steps.generate-token.outputs.token }} + token: ${{ steps.token.outputs.token }} persist-credentials: true ref: main - - name: Install uv - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 - - - name: Setup pnpm - uses: pnpm/action-setup@08c4be7e2e672a47d11bd04269e27e5f3e8529cb # v6.0.0 - - - name: Setup Node - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + - name: Setup Mise + uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2 with: - node-version-file: './server/.nvmrc' - cache: 'pnpm' - cache-dependency-path: '**/pnpm-lock.yaml' + github_token: ${{ steps.token.outputs.token }} + + # TODO move to mise + - name: Install uv + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 - name: Bump version env: @@ -124,9 +119,9 @@ jobs: steps: - name: Generate a token id: generate-token - uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1 + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 with: - app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - name: Checkout @@ -142,7 +137,7 @@ jobs: github-token: ${{ steps.generate-token.outputs.token }} - name: Create draft release - uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2.6.2 + uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0 with: draft: true tag_name: ${{ needs.bump_version.outputs.version }} diff --git a/.github/workflows/preview-label.yaml b/.github/workflows/preview-label.yaml index 5cf0008597..736507fee9 100644 --- a/.github/workflows/preview-label.yaml +++ b/.github/workflows/preview-label.yaml @@ -14,16 +14,16 @@ jobs: pull-requests: write steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 + uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1 with: - app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - - uses: mshick/add-pr-comment@64b8e914979889d746c99dea15a76e77ef64580a # v3.10.0 + - uses: immich-app/devtools/actions/sticky-comment@0135acd12ad9f3369b94a2aa3c0ae8c835a4e926 # sticky-comment-action-v1.0.0 with: - github-token: ${{ steps.token.outputs.token }} - message-id: 'preview-status' - message: 'Deploying preview environment to https://pr-${{ github.event.pull_request.number }}.preview.internal.immich.build/' + id: preview-status + token: ${{ steps.token.outputs.token }} + body: 'Deploying preview environment to https://pr-${{ github.event.pull_request.number }}.preview.internal.immich.build/' remove-label: runs-on: ubuntu-latest @@ -32,9 +32,9 @@ jobs: pull-requests: write steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 + uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1 with: - app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -48,16 +48,16 @@ jobs: name: 'preview' }) - - uses: mshick/add-pr-comment@64b8e914979889d746c99dea15a76e77ef64580a # v3.10.0 + - uses: immich-app/devtools/actions/sticky-comment@0135acd12ad9f3369b94a2aa3c0ae8c835a4e926 # sticky-comment-action-v1.0.0 if: ${{ github.event.pull_request.head.repo.fork }} with: - github-token: ${{ steps.token.outputs.token }} - message-id: 'preview-status' - message: 'PRs from forks cannot have preview environments.' + id: preview-status + token: ${{ steps.token.outputs.token }} + body: 'PRs from forks cannot have preview environments.' - - uses: mshick/add-pr-comment@64b8e914979889d746c99dea15a76e77ef64580a # v3.10.0 + - uses: immich-app/devtools/actions/sticky-comment@0135acd12ad9f3369b94a2aa3c0ae8c835a4e926 # sticky-comment-action-v1.0.0 if: ${{ !github.event.pull_request.head.repo.fork }} with: - github-token: ${{ steps.token.outputs.token }} - message-id: 'preview-status' - message: 'Preview environment has been removed.' + id: preview-status + token: ${{ steps.token.outputs.token }} + body: 'Preview environment has been removed.' diff --git a/.github/workflows/sdk.yml b/.github/workflows/sdk.yml index d9b6ffb7f5..4bff026205 100644 --- a/.github/workflows/sdk.yml +++ b/.github/workflows/sdk.yml @@ -14,34 +14,31 @@ jobs: contents: read id-token: write packages: write - defaults: - run: - working-directory: ./open-api/typescript-sdk steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 + uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1 with: - app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Checkout code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false token: ${{ steps.token.outputs.token }} - - name: Setup pnpm - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 - - # Setup .npmrc file to publish to npm - - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + - name: Setup Mise + uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2 with: - node-version-file: './open-api/typescript-sdk/.nvmrc' - registry-url: 'https://registry.npmjs.org' - cache: 'pnpm' - cache-dependency-path: '**/pnpm-lock.yaml' + github_token: ${{ steps.token.outputs.token }} + - name: Install deps - run: pnpm install --frozen-lockfile + run: pnpm --filter @immich/sdk install --frozen-lockfile + - name: Build - run: pnpm build + run: pnpm --filter @immich/sdk build + - name: Publish - run: pnpm publish --provenance --no-git-checks + env: + NPM_TAG: ${{ github.event.release.prerelease && 'rc' || 'latest' }} + run: pnpm --filter @immich/sdk publish --provenance --no-git-checks --tag "$NPM_TAG" diff --git a/.github/workflows/static_analysis.yml b/.github/workflows/static_analysis.yml index 21e5e25bc6..f3ab31d630 100644 --- a/.github/workflows/static_analysis.yml +++ b/.github/workflows/static_analysis.yml @@ -20,14 +20,14 @@ jobs: should_run: ${{ steps.check.outputs.should_run }} steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 + uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1 with: - app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - name: Check what should run id: check - uses: immich-app/devtools/actions/pre-job@f50e3b600b6ac1763ddb8f3dfc69093512b967a1 # pre-job-action-v2.0.3 + uses: immich-app/devtools/actions/pre-job@91f342bb4477c4bc10c576ae739da875d85aa164 # pre-job-action-v2.0.4 with: github-token: ${{ steps.token.outputs.token }} filters: | @@ -49,9 +49,9 @@ jobs: working-directory: ./mobile steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 + uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1 with: - app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - name: Checkout code @@ -60,38 +60,27 @@ jobs: persist-credentials: false token: ${{ steps.token.outputs.token }} - - name: Setup Flutter SDK - uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2.23.0 + - name: Setup Mise + uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2 with: - channel: 'stable' - flutter-version-file: ./mobile/pubspec.yaml + github_token: ${{ steps.token.outputs.token }} + working_directory: ./mobile - name: Install dependencies - run: dart pub get + run: flutter pub get - name: Install dependencies for UI package - run: dart pub get + run: flutter pub get working-directory: ./mobile/packages/ui - - name: Install dependencies for UI Showcase - run: dart pub get - working-directory: ./mobile/packages/ui/showcase - - - name: Install DCM - uses: CQLabs/setup-dcm@8697ae0790c0852e964a6ef1d768d62a6675481a # v2.0.1 - with: - github-token: ${{ steps.token.outputs.token }} - version: auto - working-directory: ./mobile - - - name: Generate translation file - run: dart run easy_localization:generate -S ../i18n && dart run bin/generate_keys.dart + - name: Generate translation files + run: mise //mobile:codegen:translation - name: Run Build Runner - run: make build + run: mise //mobile:codegen:dart - name: Generate platform API - run: make pigeon + run: mise //mobile:codegen:pigeon - name: Find file changes uses: tj-actions/verify-changed-files@a1c6acee9df209257a246f2cc6ae8cb6581c1edf # v20.0.4 @@ -107,20 +96,16 @@ jobs: env: CHANGED_FILES: ${{ steps.verify-changed-files.outputs.changed_files }} run: | - echo "ERROR: Generated files not up to date! Run 'make build' and 'make pigeon' inside the mobile directory" + echo "ERROR: Generated files not up to date! Run 'mise //mobile:codegen:dart' and 'mise //mobile:codegen:pigeon'" echo "Changed files: ${CHANGED_FILES}" exit 1 - - name: Run dart analyze - run: dart analyze --fatal-infos + - name: Run analyze + run: mise //mobile:analyze - - name: Run dart format - run: make format + - name: Run format + run: mise //mobile:format # TODO: Re-enable after upgrading custom_lint # - name: Run dart custom_lint # run: dart run custom_lint - - # TODO: Use https://github.com/CQLabs/dcm-action - - name: Run DCM - run: dcm analyze lib --fatal-style --fatal-warnings diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 46b1baed7e..b0ec88d5f8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,34 +17,45 @@ jobs: should_run: ${{ steps.check.outputs.should_run }} steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 + uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1 with: - app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - name: Check what should run id: check - uses: immich-app/devtools/actions/pre-job@f50e3b600b6ac1763ddb8f3dfc69093512b967a1 # pre-job-action-v2.0.3 + uses: immich-app/devtools/actions/pre-job@91f342bb4477c4bc10c576ae739da875d85aa164 # pre-job-action-v2.0.4 with: github-token: ${{ steps.token.outputs.token }} filters: | i18n: - 'i18n/**' + - 'mise.toml' web: - 'web/**' - 'i18n/**' - - 'open-api/typescript-sdk/**' + - 'packages/sdk/**' + - 'pnpm-lock.yaml' + - 'mise.toml' server: - 'server/**' + - 'pnpm-lock.yaml' + - 'mise.toml' cli: - - 'cli/**' - - 'open-api/typescript-sdk/**' + - 'packages/cli/**' + - 'packages/sdk/**' + - 'pnpm-lock.yaml' + - 'mise.toml' e2e: - 'e2e/**' + - 'pnpm-lock.yaml' + - 'mise.toml' mobile: - 'mobile/**' + - 'mise.toml' machine-learning: - 'machine-learning/**' + - 'mise.toml' .github: - '.github/**' force-filters: | @@ -58,14 +69,11 @@ jobs: runs-on: ubuntu-latest permissions: contents: read - defaults: - run: - working-directory: ./server steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 + uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1 with: - app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - name: Checkout code @@ -74,28 +82,14 @@ jobs: persist-credentials: false token: ${{ steps.token.outputs.token }} - - name: Setup pnpm - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 - - name: Setup Node - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + - name: Setup Mise + uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2 with: - node-version-file: './server/.nvmrc' - cache: 'pnpm' - cache-dependency-path: '**/pnpm-lock.yaml' - - name: Run package manager install - run: pnpm install - - name: Run linter - run: pnpm lint - if: ${{ !cancelled() }} - - name: Run formatter - run: pnpm format - if: ${{ !cancelled() }} - - name: Run tsc - run: pnpm check - if: ${{ !cancelled() }} - - name: Run small tests & coverage - run: pnpm test - if: ${{ !cancelled() }} + github_token: ${{ steps.token.outputs.token }} + + - name: Run ci-unit + run: mise run //server:ci-unit + cli-unit-tests: name: Unit Test CLI needs: pre-job @@ -105,12 +99,12 @@ jobs: contents: read defaults: run: - working-directory: ./cli + working-directory: ./packages/cli steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 + uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1 with: - app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - name: Checkout code @@ -118,31 +112,15 @@ jobs: with: persist-credentials: false token: ${{ steps.token.outputs.token }} - - name: Setup pnpm - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 - - name: Setup Node - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + + - name: Setup Mise + uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2 with: - node-version-file: './cli/.nvmrc' - cache: 'pnpm' - cache-dependency-path: '**/pnpm-lock.yaml' - - name: Setup typescript-sdk - run: pnpm install && pnpm run build - working-directory: ./open-api/typescript-sdk - - name: Install deps - run: pnpm install - - name: Run linter - run: pnpm lint - if: ${{ !cancelled() }} - - name: Run formatter - run: pnpm format - if: ${{ !cancelled() }} - - name: Run tsc - run: pnpm check - if: ${{ !cancelled() }} - - name: Run unit tests & coverage - run: pnpm test - if: ${{ !cancelled() }} + github_token: ${{ steps.token.outputs.token }} + + - name: Run ci-unit + run: mise run ci-unit + cli-unit-tests-win: name: Unit Test CLI (Windows) needs: pre-job @@ -152,12 +130,12 @@ jobs: contents: read defaults: run: - working-directory: ./cli + working-directory: ./packages/cli steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 + uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1 with: - app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - name: Checkout code @@ -165,26 +143,28 @@ jobs: with: persist-credentials: false token: ${{ steps.token.outputs.token }} - - name: Setup pnpm - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 - - name: Setup Node - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + + - name: Setup Mise + uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2 with: - node-version-file: './cli/.nvmrc' - cache: 'pnpm' - cache-dependency-path: '**/pnpm-lock.yaml' - - name: Setup typescript-sdk - run: pnpm install --frozen-lockfile && pnpm build - working-directory: ./open-api/typescript-sdk - - name: Install deps + github_token: ${{ steps.token.outputs.token }} + + - name: Run setup @immich/sdk + run: mise run //:sdk:install && mise run //:sdk:build + + - name: Run pnpm install run: pnpm install --frozen-lockfile + # Skip linter & formatter in Windows test. + - name: Run tsc run: pnpm check if: ${{ !cancelled() }} + - name: Run unit tests & coverage run: pnpm test if: ${{ !cancelled() }} + web-lint: name: Lint Web needs: pre-job @@ -197,9 +177,9 @@ jobs: working-directory: ./web steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 + uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1 with: - app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - name: Checkout code @@ -207,28 +187,22 @@ jobs: with: persist-credentials: false token: ${{ steps.token.outputs.token }} - - name: Setup pnpm - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 - - name: Setup Node - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + + - name: Setup Mise + uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2 with: - node-version-file: './web/.nvmrc' - cache: 'pnpm' - cache-dependency-path: '**/pnpm-lock.yaml' - - name: Run setup typescript-sdk - run: pnpm install --frozen-lockfile && pnpm build - working-directory: ./open-api/typescript-sdk + github_token: ${{ steps.token.outputs.token }} + + - name: Run setup @immich/sdk + run: mise run //:sdk:install && mise run //:sdk:build + - name: Run pnpm install - run: pnpm rebuild && pnpm install --frozen-lockfile + run: pnpm install --frozen-lockfile + - name: Run linter run: pnpm lint if: ${{ !cancelled() }} - - name: Run formatter - run: pnpm format - if: ${{ !cancelled() }} - - name: Run svelte checks - run: pnpm check:svelte - if: ${{ !cancelled() }} + web-unit-tests: name: Test Web needs: pre-job @@ -241,9 +215,9 @@ jobs: working-directory: ./web steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 + uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1 with: - app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - name: Checkout code @@ -251,25 +225,15 @@ jobs: with: persist-credentials: false token: ${{ steps.token.outputs.token }} - - name: Setup pnpm - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 - - name: Setup Node - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + + - name: Setup Mise + uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2 with: - node-version-file: './web/.nvmrc' - cache: 'pnpm' - cache-dependency-path: '**/pnpm-lock.yaml' - - name: Run setup typescript-sdk - run: pnpm install --frozen-lockfile && pnpm build - working-directory: ./open-api/typescript-sdk - - name: Run npm install - run: pnpm install --frozen-lockfile - - name: Run tsc - run: pnpm check:typescript - if: ${{ !cancelled() }} - - name: Run unit tests & coverage - run: pnpm test - if: ${{ !cancelled() }} + github_token: ${{ steps.token.outputs.token }} + + - name: Run ci-unit + run: mise run ci-unit + i18n-tests: name: Test i18n needs: pre-job @@ -279,9 +243,9 @@ jobs: contents: read steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 + uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1 with: - app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - name: Checkout code @@ -289,24 +253,25 @@ jobs: with: persist-credentials: false token: ${{ steps.token.outputs.token }} - - name: Setup pnpm - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 - - name: Setup Node - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + + - name: Setup Mise + uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2 with: - node-version-file: './web/.nvmrc' - cache: 'pnpm' - cache-dependency-path: '**/pnpm-lock.yaml' + github_token: ${{ steps.token.outputs.token }} + - name: Install dependencies - run: pnpm --filter=immich-i18n install --frozen-lockfile + run: pnpm -w install --frozen-lockfile + - name: Format - run: pnpm --filter=immich-i18n format:fix + run: pnpm format:fix + - name: Find file changes uses: tj-actions/verify-changed-files@a1c6acee9df209257a246f2cc6ae8cb6581c1edf # v20.0.4 id: verify-changed-files with: files: | i18n/** + - name: Verify files have not changed if: steps.verify-changed-files.outputs.files_changed == 'true' env: @@ -315,6 +280,7 @@ jobs: echo "ERROR: i18n files not up to date!" echo "Changed files: ${CHANGED_FILES}" exit 1 + e2e-tests-lint: name: End-to-End Lint needs: pre-job @@ -327,9 +293,9 @@ jobs: working-directory: ./e2e steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 + uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1 with: - app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - name: Checkout code @@ -337,30 +303,16 @@ jobs: with: persist-credentials: false token: ${{ steps.token.outputs.token }} - - name: Setup pnpm - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 - - name: Setup Node - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + + - name: Setup Mise + uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2 with: - node-version-file: './e2e/.nvmrc' - cache: 'pnpm' - cache-dependency-path: '**/pnpm-lock.yaml' - - name: Run setup typescript-sdk - run: pnpm install --frozen-lockfile && pnpm build - working-directory: ./open-api/typescript-sdk - if: ${{ !cancelled() }} - - name: Install dependencies - run: pnpm install --frozen-lockfile - if: ${{ !cancelled() }} - - name: Run linter - run: pnpm lint - if: ${{ !cancelled() }} - - name: Run formatter - run: pnpm format - if: ${{ !cancelled() }} - - name: Run tsc - run: pnpm check + github_token: ${{ steps.token.outputs.token }} + + - name: Run ci-unit + run: mise run ci-unit if: ${{ !cancelled() }} + server-medium-tests: name: Medium Tests (Server) needs: pre-job @@ -373,9 +325,9 @@ jobs: working-directory: ./server steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 + uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1 with: - app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - name: Checkout code @@ -384,21 +336,16 @@ jobs: persist-credentials: false submodules: 'recursive' token: ${{ steps.token.outputs.token }} - - name: Setup pnpm - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 - - name: Setup Node - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 - with: - node-version-file: './server/.nvmrc' - cache: 'pnpm' - cache-dependency-path: '**/pnpm-lock.yaml' + - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@035e80a7d4355d5f087ffb95db9e4a0944c04e56 # use-mise-action-v1.1.3 - - name: Run pnpm install - run: SHARP_IGNORE_GLOBAL_LIBVIPS=true pnpm install --frozen-lockfile - - name: Run medium tests - run: pnpm test:medium + uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2 + with: + github_token: ${{ steps.token.outputs.token }} + + - name: Run ci-medium + run: mise run ci-medium if: ${{ !cancelled() }} + e2e-tests-server-cli: name: End-to-End Tests (Server & CLI) needs: pre-job @@ -414,9 +361,9 @@ jobs: runner: [ubuntu-latest, ubuntu-24.04-arm] steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 + uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1 with: - app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - name: Checkout code @@ -425,52 +372,57 @@ jobs: persist-credentials: false submodules: 'recursive' token: ${{ steps.token.outputs.token }} + - name: Setup pnpm - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 + uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 + - name: Setup Node - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: - node-version-file: './e2e/.nvmrc' + node-version-file: '.nvmrc' cache: 'pnpm' cache-dependency-path: '**/pnpm-lock.yaml' - - name: Run setup typescript-sdk - run: pnpm install --frozen-lockfile && pnpm build - working-directory: ./open-api/typescript-sdk - if: ${{ !cancelled() }} + + - name: Setup packages + run: pnpm --filter @immich/sdk --filter @immich/cli install --frozen-lockfile && pnpm --filter @immich/sdk --filter @immich/cli build + - name: Run setup web run: pnpm install --frozen-lockfile && pnpm exec svelte-kit sync working-directory: ./web if: ${{ !cancelled() }} - - name: Run setup cli - run: pnpm install --frozen-lockfile && pnpm build - working-directory: ./cli - if: ${{ !cancelled() }} + - name: Install dependencies run: pnpm install --frozen-lockfile if: ${{ !cancelled() }} + - name: Start Docker Compose run: docker compose up -d --build --renew-anon-volumes --force-recreate --remove-orphans --wait --wait-timeout 300 if: ${{ !cancelled() }} + - name: Run e2e tests (api & cli) env: VITEST_DISABLE_DOCKER_SETUP: true run: pnpm test if: ${{ !cancelled() }} + - name: Run e2e tests (maintenance) env: VITEST_DISABLE_DOCKER_SETUP: true run: pnpm test:maintenance if: ${{ !cancelled() }} + - name: Capture Docker logs if: always() run: docker compose logs --no-color > docker-compose-logs.txt working-directory: ./e2e + - name: Archive Docker logs uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 if: always() with: name: e2e-server-docker-logs-${{ matrix.runner }} path: e2e/docker-compose-logs.txt + e2e-tests-web: name: End-to-End Tests (Web) needs: pre-job @@ -486,9 +438,9 @@ jobs: runner: [ubuntu-latest, ubuntu-24.04-arm] steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 + uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1 with: - app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - name: Checkout code @@ -497,70 +449,84 @@ jobs: persist-credentials: false submodules: 'recursive' token: ${{ steps.token.outputs.token }} + - name: Setup pnpm - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 + uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 + - name: Setup Node - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: - node-version-file: './e2e/.nvmrc' + node-version-file: '.nvmrc' cache: 'pnpm' cache-dependency-path: '**/pnpm-lock.yaml' - - name: Run setup typescript-sdk - run: pnpm install --frozen-lockfile && pnpm build - working-directory: ./open-api/typescript-sdk + + - name: Run setup @immich/sdk + run: pnpm --filter @immich/sdk install --frozen-lockfile && pnpm --filter @immich/sdk build + if: ${{ !cancelled() }} - name: Install dependencies run: pnpm install --frozen-lockfile if: ${{ !cancelled() }} + - name: Install Playwright Browsers run: pnpm exec playwright install chromium --only-shell if: ${{ !cancelled() }} + - name: Docker build run: docker compose up -d --build --renew-anon-volumes --force-recreate --remove-orphans --wait --wait-timeout 300 if: ${{ !cancelled() }} + - name: Run e2e tests (web) env: PLAYWRIGHT_DISABLE_WEBSERVER: true run: pnpm test:web if: ${{ !cancelled() }} + - name: Archive e2e test (web) results uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 if: success() || failure() with: name: e2e-web-test-results-${{ matrix.runner }} path: e2e/playwright-report/ + - name: Run ui tests (web) env: PLAYWRIGHT_DISABLE_WEBSERVER: true run: pnpm test:web:ui if: ${{ !cancelled() }} + - name: Archive ui test (web) results uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 if: success() || failure() with: name: e2e-ui-test-results-${{ matrix.runner }} path: e2e/playwright-report/ + - name: Run maintenance tests env: PLAYWRIGHT_DISABLE_WEBSERVER: true run: pnpm test:web:maintenance if: ${{ !cancelled() }} + - name: Archive maintenance tests (web) results uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 if: success() || failure() with: name: e2e-maintenance-isolated-test-results-${{ matrix.runner }} path: e2e/playwright-report/ + - name: Capture Docker logs if: always() run: docker compose logs --no-color > docker-compose-logs.txt working-directory: ./e2e + - name: Archive Docker logs uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 if: always() with: name: e2e-web-docker-logs-${{ matrix.runner }} path: e2e/docker-compose-logs.txt + success-check-e2e: name: End-to-End Tests Success needs: [e2e-tests-server-cli, e2e-tests-web] @@ -568,7 +534,7 @@ jobs: runs-on: ubuntu-latest if: always() steps: - - uses: immich-app/devtools/actions/success-check@53bb77345ee9f953f93bd6fd9980f07a2f24965e # success-check-action-v0.0.5 + - uses: immich-app/devtools/actions/success-check@81113db03f6d743efee81e0058c0b43f6cd6f36d # success-check-action-v0.0.6 with: needs: ${{ toJSON(needs) }} mobile-unit-tests: @@ -580,26 +546,32 @@ jobs: contents: read steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 + uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1 with: - app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false token: ${{ steps.token.outputs.token }} - - name: Setup Flutter SDK - uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2.23.0 + + - name: Setup Mise + uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2 with: - channel: 'stable' - flutter-version-file: ./mobile/pubspec.yaml - - name: Generate translation file - run: dart run easy_localization:generate -S ../i18n && dart run bin/generate_keys.dart + github_token: ${{ steps.token.outputs.token }} + working_directory: ./mobile + + - name: Install dependencies + run: flutter pub get working-directory: ./mobile + + - name: Generate translation files + run: mise //mobile:codegen:translation + - name: Run tests - working-directory: ./mobile - run: flutter test -j 1 + run: mise //mobile:test + ml-unit-tests: name: Unit Test ML needs: pre-job @@ -612,34 +584,24 @@ jobs: working-directory: ./machine-learning steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 + uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1 with: - app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false token: ${{ steps.token.outputs.token }} - - name: Install uv - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + + - name: Setup Mise + uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2 with: - python-version: 3.11 - - name: Install dependencies - run: | - uv sync --extra cpu - - name: Lint with ruff - run: | - uv run ruff check --output-format=github immich_ml - - name: Format with ruff - run: | - uv run ruff format --check immich_ml - - name: Run mypy type checking - run: | - uv run mypy --strict immich_ml/ - - name: Run tests and coverage - run: | - uv run pytest --cov=immich_ml --cov-report term-missing + github_token: ${{ steps.token.outputs.token }} + + - name: Run ci-unit + run: mise run ci-unit + github-files-formatting: name: .github Files Formatting needs: pre-job @@ -652,9 +614,9 @@ jobs: working-directory: ./.github steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 + uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1 with: - app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - name: Checkout code @@ -662,19 +624,19 @@ jobs: with: persist-credentials: false token: ${{ steps.token.outputs.token }} - - name: Setup pnpm - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 - - name: Setup Node - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + + - name: Setup Mise + uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2 with: - node-version-file: './.github/.nvmrc' - cache: 'pnpm' - cache-dependency-path: '**/pnpm-lock.yaml' + github_token: ${{ steps.token.outputs.token }} + - name: Run pnpm install run: pnpm install --frozen-lockfile + - name: Run formatter run: pnpm format if: ${{ !cancelled() }} + shellcheck: name: ShellCheck runs-on: ubuntu-latest @@ -682,9 +644,9 @@ jobs: contents: read steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 + uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1 with: - app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -703,9 +665,9 @@ jobs: contents: read steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 + uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1 with: - app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - name: Checkout code @@ -713,29 +675,27 @@ jobs: with: persist-credentials: false token: ${{ steps.token.outputs.token }} - - name: Setup pnpm - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 - - name: Setup Node - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + + - name: Setup Mise + uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2 with: - node-version-file: './server/.nvmrc' - cache: 'pnpm' - cache-dependency-path: '**/pnpm-lock.yaml' + github_token: ${{ steps.token.outputs.token }} + - name: Install server dependencies run: SHARP_IGNORE_GLOBAL_LIBVIPS=true pnpm --filter immich install --frozen-lockfile - - name: Build the app - run: pnpm --filter immich build - name: Run API generation - run: ./bin/generate-open-api.sh + run: mise //:open-api working-directory: open-api + - name: Find file changes uses: tj-actions/verify-changed-files@a1c6acee9df209257a246f2cc6ae8cb6581c1edf # v20.0.4 id: verify-changed-files with: files: | mobile/openapi - open-api/typescript-sdk + packages/sdk open-api/immich-openapi-specs.json + - name: Verify files have not changed if: steps.verify-changed-files.outputs.files_changed == 'true' env: @@ -744,6 +704,7 @@ jobs: echo "ERROR: Generated files not up to date!" echo "Changed files: ${CHANGED_FILES}" exit 1 + sql-schema-up-to-date: name: SQL Schema Checks runs-on: ubuntu-latest @@ -760,14 +721,11 @@ jobs: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 ports: - 5432:5432 - defaults: - run: - working-directory: ./server steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 + uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1 with: - app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - name: Checkout code @@ -775,31 +733,38 @@ jobs: with: persist-credentials: false token: ${{ steps.token.outputs.token }} - - name: Setup pnpm - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 - - name: Setup Node - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + + - name: Setup Mise + uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2 with: - node-version-file: './server/.nvmrc' - cache: 'pnpm' - cache-dependency-path: '**/pnpm-lock.yaml' + github_token: ${{ steps.token.outputs.token }} + - name: Install server dependencies run: SHARP_IGNORE_GLOBAL_LIBVIPS=true pnpm install --frozen-lockfile + + - name: Build plugins + run: mise //:plugins + - name: Build the app - run: pnpm build + run: mise //server:build + - name: Run existing migrations - run: pnpm migrations:run + run: pnpm --filter immich migrations:run + - name: Test npm run schema:reset command works - run: pnpm schema:reset + run: pnpm --filter immich schema:reset + - name: Generate new migrations continue-on-error: true - run: pnpm migrations:generate src/TestMigration + run: pnpm --filter migrations:generate src/TestMigration + - name: Find file changes uses: tj-actions/verify-changed-files@a1c6acee9df209257a246f2cc6ae8cb6581c1edf # v20.0.4 id: verify-changed-files with: files: | server/src + - name: Verify migration files have not changed if: steps.verify-changed-files.outputs.files_changed == 'true' env: @@ -807,18 +772,21 @@ jobs: run: | echo "ERROR: Generated migration files not up to date!" echo "Changed files: ${CHANGED_FILES}" - cat ./src/*-TestMigration.ts + cat ./server/src/*-TestMigration.ts exit 1 + - name: Run SQL generation - run: pnpm sync:sql + run: mise //:sql env: DB_URL: postgres://postgres:postgres@localhost:5432/immich + - name: Find file changes uses: tj-actions/verify-changed-files@a1c6acee9df209257a246f2cc6ae8cb6581c1edf # v20.0.4 id: verify-changed-sql-files with: files: | server/src/queries + - name: Verify SQL files have not changed if: steps.verify-changed-sql-files.outputs.files_changed == 'true' env: diff --git a/.github/workflows/weblate-lock.yml b/.github/workflows/weblate-lock.yml index 09024063c0..af321ce534 100644 --- a/.github/workflows/weblate-lock.yml +++ b/.github/workflows/weblate-lock.yml @@ -24,19 +24,19 @@ jobs: should_run: ${{ steps.check.outputs.should_run }} steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 + uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1 with: - app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - name: Check what should run id: check - uses: immich-app/devtools/actions/pre-job@f50e3b600b6ac1763ddb8f3dfc69093512b967a1 # pre-job-action-v2.0.3 + uses: immich-app/devtools/actions/pre-job@91f342bb4477c4bc10c576ae739da875d85aa164 # pre-job-action-v2.0.4 with: github-token: ${{ steps.token.outputs.token }} filters: | i18n: - - modified: 'i18n/!(en|package)**\.json' + - modified: 'i18n/!(en)**\.json' skip-force-logic: 'true' enforce-lock: @@ -47,9 +47,9 @@ jobs: if: ${{ fromJSON(needs.pre-job.outputs.should_run).i18n == true }} steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 + uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1 with: - app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - name: Bot review status @@ -68,6 +68,6 @@ jobs: permissions: {} if: always() steps: - - uses: immich-app/devtools/actions/success-check@53bb77345ee9f953f93bd6fd9980f07a2f24965e # success-check-action-v0.0.5 + - uses: immich-app/devtools/actions/success-check@81113db03f6d743efee81e0058c0b43f6cd6f36d # success-check-action-v0.0.6 with: needs: ${{ toJSON(needs) }} diff --git a/.gitignore b/.gitignore index e8fdfa266c..8beeeedfe3 100644 --- a/.gitignore +++ b/.gitignore @@ -20,7 +20,7 @@ mobile/openapi/doc mobile/openapi/.openapi-generator/FILES mobile/ios/build -open-api/typescript-sdk/build +packages/**/build mobile/android/fastlane/report.xml mobile/ios/fastlane/report.xml diff --git a/.github/.nvmrc b/.nvmrc similarity index 100% rename from .github/.nvmrc rename to .nvmrc diff --git a/i18n/.prettierrc b/.prettierrc similarity index 100% rename from i18n/.prettierrc rename to .prettierrc diff --git a/.vscode/launch.json b/.vscode/launch.json index 9ed2bb77b8..6cdc408fa2 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -23,15 +23,17 @@ "type": "node", "request": "launch", "name": "Immich CLI", - "program": "${workspaceFolder}/cli/dist/index.js", + "program": "${workspaceFolder}/packages/cli/dist/index.js", "args": ["upload", "--help"], "runtimeArgs": ["--enable-source-maps"], "console": "integratedTerminal", - "resolveSourceMapLocations": ["${workspaceFolder}/cli/dist/**/*.js.map"], + "resolveSourceMapLocations": [ + "${workspaceFolder}/packages/cli/dist/**/*.js.map" + ], "sourceMaps": true, - "outFiles": ["${workspaceFolder}/cli/dist/**/*.js"], + "outFiles": ["${workspaceFolder}/packages/cli/dist/**/*.js"], "skipFiles": ["/**"], - "preLaunchTask": "Build Immich CLI" + "preLaunchTask": "Build @immich/cli" } ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index dbf9688b9b..e20930cacf 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -29,6 +29,9 @@ "editor.formatOnSave": true, "tailwindCSS.lint.suggestCanonicalClasses": "ignore" }, + "svelte.plugin.svelte.compilerWarnings": { + "state_referenced_locally": "ignore" + }, "[typescript]": { "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.formatOnSave": true diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index 1484498281..0000000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,134 +0,0 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -We as members, contributors, and leaders pledge to make participation -in our community a harassment-free experience for everyone, regardless -of age, body size, visible or invisible disability, ethnicity, sex -characteristics, gender identity and expression, level of experience, -education, socio-economic status, nationality, personal appearance, -race, religion, or sexual identity and orientation. - -We pledge to act and interact in ways that contribute to an open, -welcoming, diverse, inclusive, and healthy community. - -## Our Standards - -Examples of behavior that contributes to a positive environment for -our community include: - -- Demonstrating empathy and kindness toward other people -- Being respectful of differing opinions, viewpoints, and experiences -- Giving and gracefully accepting constructive feedback -- Accepting responsibility and apologizing to those affected by our - mistakes, and learning from the experience -- Focusing on what is best not just for us as individuals, but for the - overall community - -Examples of unacceptable behavior include: - -- The use of sexualized language or imagery, and sexual attention or - advances of any kind -- Trolling, insulting or derogatory comments, and personal or - political attacks -- Public or private harassment -- Publishing others' private information, such as a physical or email - address, without their explicit permission -- Other conduct which could reasonably be considered inappropriate in - a professional setting - -## Enforcement Responsibilities - -Community leaders are responsible for clarifying and enforcing our -standards of acceptable behavior and will take appropriate and fair -corrective action in response to any behavior that they deem -inappropriate, threatening, offensive, or harmful. - -Community leaders have the right and responsibility to remove, edit, -or reject comments, commits, code, wiki edits, issues, and other -contributions that are not aligned to this Code of Conduct, and will -communicate reasons for moderation decisions when appropriate. - -## Scope - -This Code of Conduct applies within all community spaces, and also -applies when an individual is officially representing the community in -public spaces. Examples of representing our community include using an -official e-mail address, posting via an official social media account, -or acting as an appointed representative at an online or offline -event. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior -may be reported to the community leaders responsible for enforcement -at our Discord channel. All complaints -will be reviewed and investigated promptly and fairly. - -All community leaders are obligated to respect the privacy and -security of the reporter of any incident. - -## Enforcement Guidelines - -Community leaders will follow these Community Impact Guidelines in -determining the consequences for any action they deem in violation of -this Code of Conduct: - -### 1. Correction - -**Community Impact**: Use of inappropriate language or other behavior -deemed unprofessional or unwelcome in the community. - -**Consequence**: A private, written warning from community leaders, -providing clarity around the nature of the violation and an -explanation of why the behavior was inappropriate. A public apology -may be requested. - -### 2. Warning - -**Community Impact**: A violation through a single incident or series -of actions. - -**Consequence**: A warning with consequences for continued -behavior. No interaction with the people involved, including -unsolicited interaction with those enforcing the Code of Conduct, for -a specified period of time. This includes avoiding interactions in -community spaces as well as external channels like social -media. Violating these terms may lead to a temporary or permanent ban. - -### 3. Temporary Ban - -**Community Impact**: A serious violation of community standards, -including sustained inappropriate behavior. - -**Consequence**: A temporary ban from any sort of interaction or -public communication with the community for a specified period of -time. No public or private interaction with the people involved, -including unsolicited interaction with those enforcing the Code of -Conduct, is allowed during this period. Violating these terms may lead -to a permanent ban. - -### 4. Permanent Ban - -**Community Impact**: Demonstrating a pattern of violation of -community standards, including sustained inappropriate behavior, -harassment of an individual, or aggression toward or disparagement of -classes of individuals. - -**Consequence**: A permanent ban from any sort of public interaction -within the community. - -## Attribution - -This Code of Conduct is adapted from the [Contributor -Covenant][homepage], version 2.0, available at -https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. - -Community Impact Guidelines were inspired by [Mozilla's code of -conduct enforcement ladder](https://github.com/mozilla/diversity). - -[homepage]: https://www.contributor-covenant.org - -For answers to common questions about this code of conduct, see the -FAQ at https://www.contributor-covenant.org/faq. Translations are -available at https://www.contributor-covenant.org/translations. diff --git a/Makefile b/Makefile index 4d76913d8f..eb8bad09f3 100644 --- a/Makefile +++ b/Makefile @@ -1,152 +1,58 @@ dev: - @trap 'make dev-down' EXIT; COMPOSE_BAKE=true docker compose -f ./docker/docker-compose.dev.yml up --remove-orphans + @printf "This command has been removed. Please use:\n\n mise dev # or mise //:dev from another directory\n\n" >&2 && exit 1 dev-down: - docker compose -f ./docker/docker-compose.dev.yml down --remove-orphans + @printf "This command has been removed. Please use:\n\n mise dev-down # or mise //:dev-down from another directory\n\n" >&2 && exit 1 dev-update: - @trap 'make dev-down' EXIT; COMPOSE_BAKE=true docker compose -f ./docker/docker-compose.dev.yml up --build -V --remove-orphans + @printf "This command has been removed. Please use:\n\n mise dev-update # or mise //:dev-update from another directory\n\n" >&2 && exit 1 dev-scale: - @trap 'make dev-down' EXIT; COMPOSE_BAKE=true docker compose -f ./docker/docker-compose.dev.yml up --build -V --scale immich-server=3 --remove-orphans + @printf "This command has been removed. Please use:\n\n mise dev-scale # or mise //:dev-scale from another directory\n\n" >&2 && exit 1 dev-docs: npm --prefix docs run start .PHONY: e2e e2e: - @trap 'make e2e-down' EXIT; COMPOSE_BAKE=true docker compose -f ./e2e/docker-compose.yml up --remove-orphans + @printf "This command has been removed. Please use:\n\n mise e2e # or mise //:e2e from another directory\n\n" >&2 && exit 1 e2e-dev: - @trap 'make e2e-down' EXIT; COMPOSE_BAKE=true docker compose -f ./e2e/docker-compose.dev.yml up --remove-orphans + @printf "This command has been removed. Please use:\n\n mise e2e-dev # or mise //:e2e-dev from another directory\n\n" >&2 && exit 1 e2e-update: - @trap 'make e2e-down' EXIT; COMPOSE_BAKE=true docker compose -f ./e2e/docker-compose.yml up --build -V --remove-orphans + @printf "This command has been removed. Please use:\n\n mise e2e-update # or mise //:e2e-update from another directory\n\n" >&2 && exit 1 e2e-down: - docker compose -f ./e2e/docker-compose.yml down --remove-orphans + @printf "This command has been removed. Please use:\n\n mise e2e-down # or mise //:e2e-down from another directory\n\n" >&2 && exit 1 prod: - @trap 'make prod-down' EXIT; COMPOSE_BAKE=true docker compose -f ./docker/docker-compose.prod.yml up --build -V --remove-orphans + @printf "This command has been removed. Please use:\n\n mise prod # or mise //:prod from another directory\n\n" >&2 && exit 1 prod-down: - docker compose -f ./docker/docker-compose.prod.yml down --remove-orphans + @printf "This command has been removed. Please use:\n\n mise prod-down # or mise //:prod-down from another directory\n\n" >&2 && exit 1 prod-scale: - @trap 'make prod-down' EXIT; COMPOSE_BAKE=true docker compose -f ./docker/docker-compose.prod.yml up --build -V --scale immich-server=3 --scale immich-microservices=3 --remove-orphans + @printf "This command has been removed. Please use:\n\n mise prod-scale # or mise //:prod-scale from another directory\n\n" >&2 && exit 1 .PHONY: open-api open-api: - cd ./open-api && bash ./bin/generate-open-api.sh - -open-api-dart: - cd ./open-api && bash ./bin/generate-open-api.sh dart - -open-api-typescript: - cd ./open-api && bash ./bin/generate-open-api.sh typescript + @printf "This command has been removed. Please use:\n\n mise open-api # or mise //:open-api from another directory\n\n" >&2 && exit 1 sql: - pnpm --filter immich run sync:sql + @printf "This command has been removed. Please use:\n\n mise sql # or mise //:sql from another directory\n\n" >&2 && exit 1 -attach-server: - docker exec -it docker_immich-server_1 sh renovate: LOG_LEVEL=debug pnpm exec renovate --platform=local --repository-cache=reset -# Directories that need to be created for volumes or build output -VOLUME_DIRS = \ - ./.pnpm-store \ - ./web/.svelte-kit \ - ./web/node_modules \ - ./web/coverage \ - ./e2e/node_modules \ - ./docs/node_modules \ - ./server/node_modules \ - ./open-api/typescript-sdk/node_modules \ - ./.github/node_modules \ - ./node_modules \ - ./cli/node_modules - # Include .env file if it exists -include docker/.env MODULES = e2e server web cli sdk docs .github -# directory to package name mapping function -# cli = @immich/cli -# docs = documentation -# e2e = immich-e2e -# open-api/typescript-sdk = @immich/sdk -# server = immich -# web = immich-web -map-package = $(subst sdk,@immich/sdk,$(subst cli,@immich/cli,$(subst docs,documentation,$(subst e2e,immich-e2e,$(subst server,immich,$(subst web,immich-web,$1)))))) - -audit-%: - pnpm --filter $(call map-package,$*) audit fix -install-%: - pnpm --filter $(call map-package,$*) install $(if $(FROZEN),--frozen-lockfile) $(if $(OFFLINE),--offline) -build-cli: build-sdk -build-web: build-sdk -build-%: install-% - pnpm --filter $(call map-package,$*) run build -format-%: - pnpm --filter $(call map-package,$*) run format:fix -lint-%: - pnpm --filter $(call map-package,$*) run lint:fix -check-%: - pnpm --filter $(call map-package,$*) run check -check-web: - pnpm --filter immich-web run check:typescript - pnpm --filter immich-web run check:svelte -test-%: - pnpm --filter $(call map-package,$*) run test test-e2e: - docker compose -f ./e2e/docker-compose.yml build - pnpm --filter immich-e2e run test - pnpm --filter immich-e2e run test:web -test-medium: - docker run \ - --rm \ - -v ./server/src:/usr/src/app/src \ - -v ./server/test:/usr/src/app/test \ - -v ./server/vitest.config.medium.mjs:/usr/src/app/vitest.config.medium.mjs \ - -v ./server/tsconfig.json:/usr/src/app/tsconfig.json \ - -e NODE_ENV=development \ - immich-server:latest \ - -c "pnpm test:medium -- --run" -test-medium-dev: - docker exec -it immich_server /bin/sh -c "pnpm run test:medium" - -install-all: - pnpm -r --filter '!documentation' install - -build-all: $(foreach M,$(filter-out e2e docs .github,$(MODULES)),build-$M) ; - -check-all: - pnpm -r --filter '!documentation' run "/^(check|check\:svelte|check\:typescript)$/" -lint-all: - pnpm -r --filter '!documentation' run lint:fix -format-all: - pnpm -r --filter '!documentation' run format:fix -audit-all: - pnpm -r --filter '!documentation' audit fix -hygiene-all: audit-all - pnpm -r --filter '!documentation' run "/(format:fix|check|check:svelte|check:typescript|sql)/" - -test-all: - pnpm -r --filter '!documentation' run "/^test/" + @printf "This command has been removed. Please use:\n\n mise //e2e:test # or mise //e2e:test-web for web tests, respectively\n\n" >&2 && exit 1 clean: - find . -name "node_modules" -type d -prune -exec rm -rf {} + - find . -name "dist" -type d -prune -exec rm -rf '{}' + - find . -name "build" -type d -prune -exec rm -rf '{}' + - find . -name ".svelte-kit" -type d -prune -exec rm -rf '{}' + - find . -name "coverage" -type d -prune -exec rm -rf '{}' + - find . -name ".pnpm-store" -type d -prune -exec rm -rf '{}' + - command -v docker >/dev/null 2>&1 && docker compose -f ./docker/docker-compose.dev.yml down -v --remove-orphans || true - command -v docker >/dev/null 2>&1 && docker compose -f ./e2e/docker-compose.yml down -v --remove-orphans || true - - -setup-server-dev: install-server -setup-web-dev: install-sdk build-sdk install-web + @printf "This command has been removed. Please use:\n\n mise clean # or mise //:clean from another directory\n\n" >&2 && exit 1 diff --git a/SECURITY.md b/SECURITY.md deleted file mode 100644 index 1877440a8d..0000000000 --- a/SECURITY.md +++ /dev/null @@ -1,5 +0,0 @@ -# Security Policy - -## Reporting a Vulnerability - -Please report security issues to `security@immich.app` diff --git a/cli/.nvmrc b/cli/.nvmrc deleted file mode 100644 index 5bf4400f22..0000000000 --- a/cli/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -24.15.0 diff --git a/cli/Dockerfile b/cli/Dockerfile deleted file mode 100644 index d56190ee16..0000000000 --- a/cli/Dockerfile +++ /dev/null @@ -1,14 +0,0 @@ -FROM node:24.1.0-alpine3.20@sha256:8fe019e0d57dbdce5f5c27c0b63d2775cf34b00e3755a7dea969802d7e0c2b25 AS core - -WORKDIR /usr/src/app -COPY package* pnpm* .pnpmfile.cjs ./ -COPY ./cli ./cli/ -COPY ./open-api/typescript-sdk ./open-api/typescript-sdk/ -RUN corepack enable pnpm && \ - pnpm install --filter @immich/sdk --filter @immich/cli --frozen-lockfile && \ - pnpm --filter @immich/sdk build && \ - pnpm --filter @immich/cli build - -WORKDIR /import - -ENTRYPOINT ["node", "/usr/src/app/cli/dist"] diff --git a/deployment/mise.lock b/deployment/mise.lock new file mode 100644 index 0000000000..fb7be27b99 --- /dev/null +++ b/deployment/mise.lock @@ -0,0 +1,65 @@ +# @generated - this file is auto-generated by `mise lock` https://mise.en.dev/dev-tools/mise-lock.html + +[[tools.opentofu]] +version = "1.11.6" +backend = "aqua:opentofu/opentofu" + +[tools.opentofu."platforms.linux-arm64"] +checksum = "sha256:d4f2ab15776925864b049bb329d69682851de6f5204f256e9fa86d07a0308850" +url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_arm64.tar.gz" + +[tools.opentofu."platforms.linux-arm64-musl"] +checksum = "sha256:d4f2ab15776925864b049bb329d69682851de6f5204f256e9fa86d07a0308850" +url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_arm64.tar.gz" + +[tools.opentofu."platforms.linux-x64"] +checksum = "sha256:02800fafa2753a9f50c38483e2fdf5bc353fd62895eb9e25eec9a5145df3a69e" +url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_amd64.tar.gz" + +[tools.opentofu."platforms.linux-x64-musl"] +checksum = "sha256:02800fafa2753a9f50c38483e2fdf5bc353fd62895eb9e25eec9a5145df3a69e" +url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_amd64.tar.gz" + +[tools.opentofu."platforms.macos-arm64"] +checksum = "sha256:62d7fa8539e13b444827aa0a3b90c5972da5c47e8f8882d9dcf2e430e78840c1" +url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_darwin_arm64.tar.gz" + +[tools.opentofu."platforms.macos-x64"] +checksum = "sha256:1408cdef1c380f914565e6b4bb70794c6b163f195fcb233357f3d6c5745906b6" +url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_darwin_amd64.tar.gz" + +[tools.opentofu."platforms.windows-x64"] +checksum = "sha256:27323f70c875b8251bfd7e61a4cffc3ebff4e56ed1e611b955016f0c7077367e" +url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_windows_amd64.tar.gz" + +[[tools.terragrunt]] +version = "1.0.3" +backend = "aqua:gruntwork-io/terragrunt" + +[tools.terragrunt."platforms.linux-arm64"] +checksum = "sha256:e5b60ab05b5214db694e6bc215d8124fb626e277cdb56b86f6147ae110d510fe" +url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_arm64.tar.gz" + +[tools.terragrunt."platforms.linux-arm64-musl"] +checksum = "sha256:e5b60ab05b5214db694e6bc215d8124fb626e277cdb56b86f6147ae110d510fe" +url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_arm64.tar.gz" + +[tools.terragrunt."platforms.linux-x64"] +checksum = "sha256:6d48049baf82e0bf9c804368dc85cbfeadc10955e33777e9e8de3e020b94b073" +url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_amd64.tar.gz" + +[tools.terragrunt."platforms.linux-x64-musl"] +checksum = "sha256:6d48049baf82e0bf9c804368dc85cbfeadc10955e33777e9e8de3e020b94b073" +url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_amd64.tar.gz" + +[tools.terragrunt."platforms.macos-arm64"] +checksum = "sha256:aacb5be2ca5475300cbce246dfbd8a45eb47510fbaa70fab8561c49ef5db03aa" +url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_darwin_arm64.tar.gz" + +[tools.terragrunt."platforms.macos-x64"] +checksum = "sha256:3133c2251e191aede8e3dd2a5b3aee2e91c5f08f88f117aee40eed9a24c8ef6b" +url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_darwin_amd64.tar.gz" + +[tools.terragrunt."platforms.windows-x64"] +checksum = "sha256:183b2745b4e04980a6bfa4450ff81956a12596ca22d70f7aaa793980f5b036db" +url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_windows_amd64.exe.tar.gz" diff --git a/deployment/mise.toml b/deployment/mise.toml index 61a50bb666..fadc28dc2e 100644 --- a/deployment/mise.toml +++ b/deployment/mise.toml @@ -1,5 +1,5 @@ [tools] -terragrunt = "1.0.2" +terragrunt = "1.0.3" opentofu = "1.11.6" [tasks."tg:fmt"] diff --git a/deployment/modules/cloudflare/docs-release/.terraform.lock.hcl b/deployment/modules/cloudflare/docs-release/.terraform.lock.hcl index 0869dd28bc..7fdd96847c 100644 --- a/deployment/modules/cloudflare/docs-release/.terraform.lock.hcl +++ b/deployment/modules/cloudflare/docs-release/.terraform.lock.hcl @@ -2,37 +2,37 @@ # Manual edits may be lost in future updates. provider "registry.opentofu.org/cloudflare/cloudflare" { - version = "4.52.5" - constraints = "4.52.5" + version = "4.52.7" + constraints = "4.52.7" hashes = [ - "h1:+rfzF+16ZcWZWnTyW/p1HHTzYbPKX8Zt2nIFtR/+f+E=", - "h1:18bXaaOSq8MWKuMxo/4y7EB7/i7G90y5QsKHZRmkoDo=", - "h1:4vZVOpKeEQZsF2VrARRZFeL37Ed/gD4rRMtfnvWQres=", - "h1:BZOsTF83QPKXTAaYqxPKzdl1KRjk/L2qbPpFjM0w28A=", - "h1:CDuC+HXLvc1z6wkCRsSDcc/+QENIHEtssYshiWg3opA=", - "h1:DE+YFzLnqSe79pI2R4idRGx5QzLdrA7RXvngTkGfZ30=", - "h1:DfaJwH3Ml4yrRbdAY4AcDVy0QTQk5T3A622TXzS/u2E=", - "h1:EIDXP0W3kgIv2pecrFmqtK/DnlqkyckzBzhxKaXU+4A=", - "h1:EV4kYyaOnwGA0bh/3hU6Ezqnt1PFDxopH7i85e48IzY=", - "h1:M0iXabfzamU+MPDi0G9XACpbacFKMakmM+Z9HZ8HrsM=", - "h1:YWmCbGF/KbsrUzcYVBLscwLizidbp95TDQa0N2qpmVo=", - "h1:cxPcCB5gbrpUO1+IXkQYs1YTY50/0IlApCzGea0cwuQ=", - "h1:g6DldikTV2HXUu9uoeNY5FuLufgaYWF4ufgZg7wq62s=", - "h1:oi/Hrx9pwoQ+Z52CBC+rrowVH387EIj0qvnxQgDeI+0=", - "zh:1a3400cb38863b2585968d1876706bcfc67a148e1318a1d325c6c7704adc999b", - "zh:4c5062cb9e9da1676f06ae92b8370186d98976cc4c7030d3cd76df12af54282a", - "zh:52110f493b5f0587ef77a1cfd1a67001fd4c617b14c6502d732ab47352bdc2f7", - "zh:5aa536f9eaeb43823aaf2aa80e7d39b25ef2b383405ed034aa16a28b446a9238", - "zh:5cc39459a1c6be8a918f17054e4fbba573825ed5597dcada588fe99614d98a5b", - "zh:629ae6a7ba298815131da826474d199312d21cec53a4d5ded4fa56a692e6f072", - "zh:719cc7c75dc1d3eb30c22ff5102a017996d9788b948078c7e1c5b3446aeca661", - "zh:8698635a3ca04383c1e93b21d6963346bdae54d27177a48e4b1435b7f731731c", + "h1:+O72J3QYiZtYmYYZM/Eh0f4NNfl1BvjX1eju43qTQsQ=", + "h1:0oqjYIPXcXh7XiDiKI085cHDYQQ5mh8kDl9dmBtvtog=", + "h1:4b4ESb87MGv5bnadgYe7sK5rEkKMZhbkQcwPubQTsR4=", + "h1:6mTr3eA1Ddb348lLmJuyvn98z4KF+ejqaUEJ76D1rzQ=", + "h1:9/3YH+9k9HqsvFtbmBf7SO2+xqZeZrXNKzLkjNuhUEA=", + "h1:Jcq4tBWgyH4/2JsojNBSRaN0mcItVMchO+lynonrlqc=", + "h1:Y4Vv/2RdP0Q+uxqhOxzOdKxuuEMjXPDcU0vPc5bCQzI=", + "h1:a0gW8FBKsbP9Fi0HEDoy49WIbEWVHk9+BR4/iwuBdDQ=", + "h1:gElv6iqJtg8OKN77gbw+MjrkrQmJHPkkMEi1J+0xkpU=", + "h1:oslXUugD/NQ+duJgT4BhKQyfGbuFOANknMvR73fiOeM=", + "h1:pPItIWii5oymR+geZB219ROSPuSODPLTlM4S/u8xLvM=", + "h1:u67GWw8GwD9NDlDzp9Y5VRnSQGcCrE8rSpkGPaBpDl0=", + "h1:uUUa9dY0XQOycI8pxg16PFFtL0WCTi9uEJz8trTQ7pU=", + "h1:y3rV8KF2q6GEMANNlf5EkKJurlfbKlIKpjGcdxoy7pQ=", + "zh:0c904ce31a4c6c4a5b3bf7ff1560e77c0cc7e2450c8553ded8e8c90398e1418b", + "zh:36183d310c36373fe4cb936b83c595c6fd3b0a94bc7827f28e5789ccbf59752e", + "zh:556a568a6f0235e8f41647de9e4d3a1e7b1d6502df8b19b54ec441f1c653ea10", + "zh:633ebbd5b0245e75e500ef9be4d9e62288f97e8da3baaa51323892a786d90285", + "zh:6acfe60cf52a65ba8f044f748548d2119e7f4fd7f8ebcb14698960d87c68f529", "zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f", - "zh:8a9993f1dcadf1dd6ca43b23348abe374605d29945a2fafc07fb3457644e6a54", - "zh:b1b9a1e6bcc24d5863a664a411d2dc906373ae7a2399d2d65548ce7377057852", - "zh:b270184cdeec277218e84b94cb136fead753da717f9b9dc378e51907f3f00bb0", - "zh:dff2bc10071210181726ce270f954995fe42c696e61e2e8f874021fed02521e5", - "zh:e8e87b40b6a87dc097b0fdc20d3f725cec0d82abc9cc3755c1f89f8f6e8b0036", - "zh:ee964a6573d399a5dd22ce328fb38ca1207797a02248f14b2e4913ee390e7803", + "zh:904acc31ebb9d6ef68c792074b30532ee61bf515f19e0a3c75b46f126cca1f13", + "zh:a1d0a81246afc8750286d3f6fe7a8fbe6460dd2662407b28dbfbabb612e5fa9d", + "zh:a41a36fe253fc365fe2b7ffc749624688b2693b4634862fda161179ab100029f", + "zh:a7ef269e77ffa8715c8945a2c14322c7ff159ea44c15f62505f3cbb2cae3b32d", + "zh:b01aa3bed30610633b762df64332b26f8844a68c3960cebcb30f04918efc67fe", + "zh:b069cc2cd18cae10757df3ae030508eac8d55de7e49eda7a5e3e11f2f7fe6455", + "zh:b2d2c6313729ebb7465dceece374049e2d08bda34473901be9ff46a8836d42b2", + "zh:db0e114edaf4bc2f3d4769958807c83022bfbc619a00bdf4c4bd17faa4ab2d8b", + "zh:ecc0aa8b9044f664fd2aaf8fa992d976578f78478980555b4b8f6148e8d1a5fe", ] } diff --git a/deployment/modules/cloudflare/docs-release/config.tf b/deployment/modules/cloudflare/docs-release/config.tf index 63347cf67e..7c59cdd2e3 100644 --- a/deployment/modules/cloudflare/docs-release/config.tf +++ b/deployment/modules/cloudflare/docs-release/config.tf @@ -5,7 +5,7 @@ terraform { required_providers { cloudflare = { source = "cloudflare/cloudflare" - version = "4.52.5" + version = "4.52.7" } } } diff --git a/deployment/modules/cloudflare/docs/.terraform.lock.hcl b/deployment/modules/cloudflare/docs/.terraform.lock.hcl index 0869dd28bc..7fdd96847c 100644 --- a/deployment/modules/cloudflare/docs/.terraform.lock.hcl +++ b/deployment/modules/cloudflare/docs/.terraform.lock.hcl @@ -2,37 +2,37 @@ # Manual edits may be lost in future updates. provider "registry.opentofu.org/cloudflare/cloudflare" { - version = "4.52.5" - constraints = "4.52.5" + version = "4.52.7" + constraints = "4.52.7" hashes = [ - "h1:+rfzF+16ZcWZWnTyW/p1HHTzYbPKX8Zt2nIFtR/+f+E=", - "h1:18bXaaOSq8MWKuMxo/4y7EB7/i7G90y5QsKHZRmkoDo=", - "h1:4vZVOpKeEQZsF2VrARRZFeL37Ed/gD4rRMtfnvWQres=", - "h1:BZOsTF83QPKXTAaYqxPKzdl1KRjk/L2qbPpFjM0w28A=", - "h1:CDuC+HXLvc1z6wkCRsSDcc/+QENIHEtssYshiWg3opA=", - "h1:DE+YFzLnqSe79pI2R4idRGx5QzLdrA7RXvngTkGfZ30=", - "h1:DfaJwH3Ml4yrRbdAY4AcDVy0QTQk5T3A622TXzS/u2E=", - "h1:EIDXP0W3kgIv2pecrFmqtK/DnlqkyckzBzhxKaXU+4A=", - "h1:EV4kYyaOnwGA0bh/3hU6Ezqnt1PFDxopH7i85e48IzY=", - "h1:M0iXabfzamU+MPDi0G9XACpbacFKMakmM+Z9HZ8HrsM=", - "h1:YWmCbGF/KbsrUzcYVBLscwLizidbp95TDQa0N2qpmVo=", - "h1:cxPcCB5gbrpUO1+IXkQYs1YTY50/0IlApCzGea0cwuQ=", - "h1:g6DldikTV2HXUu9uoeNY5FuLufgaYWF4ufgZg7wq62s=", - "h1:oi/Hrx9pwoQ+Z52CBC+rrowVH387EIj0qvnxQgDeI+0=", - "zh:1a3400cb38863b2585968d1876706bcfc67a148e1318a1d325c6c7704adc999b", - "zh:4c5062cb9e9da1676f06ae92b8370186d98976cc4c7030d3cd76df12af54282a", - "zh:52110f493b5f0587ef77a1cfd1a67001fd4c617b14c6502d732ab47352bdc2f7", - "zh:5aa536f9eaeb43823aaf2aa80e7d39b25ef2b383405ed034aa16a28b446a9238", - "zh:5cc39459a1c6be8a918f17054e4fbba573825ed5597dcada588fe99614d98a5b", - "zh:629ae6a7ba298815131da826474d199312d21cec53a4d5ded4fa56a692e6f072", - "zh:719cc7c75dc1d3eb30c22ff5102a017996d9788b948078c7e1c5b3446aeca661", - "zh:8698635a3ca04383c1e93b21d6963346bdae54d27177a48e4b1435b7f731731c", + "h1:+O72J3QYiZtYmYYZM/Eh0f4NNfl1BvjX1eju43qTQsQ=", + "h1:0oqjYIPXcXh7XiDiKI085cHDYQQ5mh8kDl9dmBtvtog=", + "h1:4b4ESb87MGv5bnadgYe7sK5rEkKMZhbkQcwPubQTsR4=", + "h1:6mTr3eA1Ddb348lLmJuyvn98z4KF+ejqaUEJ76D1rzQ=", + "h1:9/3YH+9k9HqsvFtbmBf7SO2+xqZeZrXNKzLkjNuhUEA=", + "h1:Jcq4tBWgyH4/2JsojNBSRaN0mcItVMchO+lynonrlqc=", + "h1:Y4Vv/2RdP0Q+uxqhOxzOdKxuuEMjXPDcU0vPc5bCQzI=", + "h1:a0gW8FBKsbP9Fi0HEDoy49WIbEWVHk9+BR4/iwuBdDQ=", + "h1:gElv6iqJtg8OKN77gbw+MjrkrQmJHPkkMEi1J+0xkpU=", + "h1:oslXUugD/NQ+duJgT4BhKQyfGbuFOANknMvR73fiOeM=", + "h1:pPItIWii5oymR+geZB219ROSPuSODPLTlM4S/u8xLvM=", + "h1:u67GWw8GwD9NDlDzp9Y5VRnSQGcCrE8rSpkGPaBpDl0=", + "h1:uUUa9dY0XQOycI8pxg16PFFtL0WCTi9uEJz8trTQ7pU=", + "h1:y3rV8KF2q6GEMANNlf5EkKJurlfbKlIKpjGcdxoy7pQ=", + "zh:0c904ce31a4c6c4a5b3bf7ff1560e77c0cc7e2450c8553ded8e8c90398e1418b", + "zh:36183d310c36373fe4cb936b83c595c6fd3b0a94bc7827f28e5789ccbf59752e", + "zh:556a568a6f0235e8f41647de9e4d3a1e7b1d6502df8b19b54ec441f1c653ea10", + "zh:633ebbd5b0245e75e500ef9be4d9e62288f97e8da3baaa51323892a786d90285", + "zh:6acfe60cf52a65ba8f044f748548d2119e7f4fd7f8ebcb14698960d87c68f529", "zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f", - "zh:8a9993f1dcadf1dd6ca43b23348abe374605d29945a2fafc07fb3457644e6a54", - "zh:b1b9a1e6bcc24d5863a664a411d2dc906373ae7a2399d2d65548ce7377057852", - "zh:b270184cdeec277218e84b94cb136fead753da717f9b9dc378e51907f3f00bb0", - "zh:dff2bc10071210181726ce270f954995fe42c696e61e2e8f874021fed02521e5", - "zh:e8e87b40b6a87dc097b0fdc20d3f725cec0d82abc9cc3755c1f89f8f6e8b0036", - "zh:ee964a6573d399a5dd22ce328fb38ca1207797a02248f14b2e4913ee390e7803", + "zh:904acc31ebb9d6ef68c792074b30532ee61bf515f19e0a3c75b46f126cca1f13", + "zh:a1d0a81246afc8750286d3f6fe7a8fbe6460dd2662407b28dbfbabb612e5fa9d", + "zh:a41a36fe253fc365fe2b7ffc749624688b2693b4634862fda161179ab100029f", + "zh:a7ef269e77ffa8715c8945a2c14322c7ff159ea44c15f62505f3cbb2cae3b32d", + "zh:b01aa3bed30610633b762df64332b26f8844a68c3960cebcb30f04918efc67fe", + "zh:b069cc2cd18cae10757df3ae030508eac8d55de7e49eda7a5e3e11f2f7fe6455", + "zh:b2d2c6313729ebb7465dceece374049e2d08bda34473901be9ff46a8836d42b2", + "zh:db0e114edaf4bc2f3d4769958807c83022bfbc619a00bdf4c4bd17faa4ab2d8b", + "zh:ecc0aa8b9044f664fd2aaf8fa992d976578f78478980555b4b8f6148e8d1a5fe", ] } diff --git a/deployment/modules/cloudflare/docs/config.tf b/deployment/modules/cloudflare/docs/config.tf index 63347cf67e..7c59cdd2e3 100644 --- a/deployment/modules/cloudflare/docs/config.tf +++ b/deployment/modules/cloudflare/docs/config.tf @@ -5,7 +5,7 @@ terraform { required_providers { cloudflare = { source = "cloudflare/cloudflare" - version = "4.52.5" + version = "4.52.7" } } } diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml index 434500b835..2398b971dd 100644 --- a/docker/docker-compose.dev.yml +++ b/docker/docker-compose.dev.yml @@ -21,14 +21,14 @@ services: volumes: - ..:/usr/src/app # - ../../ui:/usr/src/ui - - pnpm_cache:/buildcache/pnpm_cache + - build_cache:/buildcache - server_node_modules:/usr/src/app/server/node_modules - web_node_modules:/usr/src/app/web/node_modules - github_node_modules:/usr/src/app/.github/node_modules - - cli_node_modules:/usr/src/app/cli/node_modules + - cli_node_modules:/usr/src/app/packages/cli/node_modules - docs_node_modules:/usr/src/app/docs/node_modules - e2e_node_modules:/usr/src/app/e2e/node_modules - - sdk_node_modules:/usr/src/app/open-api/typescript-sdk/node_modules + - sdk_node_modules:/usr/src/app/packages/sdk/node_modules - app_node_modules:/usr/src/app/node_modules - sveltekit:/usr/src/app/web/.svelte-kit - coverage:/usr/src/app/web/coverage @@ -45,11 +45,11 @@ services: target: dev command: - | - pnpm install + mise install touch /tmp/init-complete exec tail -f /dev/null volumes: - - pnpm_store_server:/buildcache/pnpm-store + - build_cache:/buildcache restart: 'no' healthcheck: test: ['CMD', 'test', '-f', '/tmp/init-complete'] @@ -73,8 +73,7 @@ services: volumes: - ${UPLOAD_LOCATION}/photos:/data - /etc/localtime:/etc/localtime:ro - - pnpm_store_server:/buildcache/pnpm-store - - ../plugins:/build/corePlugin + - ../packages/plugin-core:/build/plugins/immich-plugin-core env_file: - .env environment: @@ -122,8 +121,6 @@ services: ports: - 3000:3000 - 24678:24678 - volumes: - - pnpm_store_web:/buildcache/pnpm-store restart: unless-stopped depends_on: immich-init: @@ -157,7 +154,7 @@ services: redis: container_name: immich_redis - image: docker.io/valkey/valkey:9@sha256:3b55fbaa0cd93cf0d9d961f405e4dfcc70efe325e2d84da207a0a8e6d8fde4f9 + image: docker.io/valkey/valkey:9@sha256:4963247afc4cd33c7d3b2d2816b9f7f8eeebab148d29056c2ca4d7cbc966f2d9 healthcheck: test: redis-cli ping || exit 1 @@ -203,9 +200,7 @@ volumes: model_cache: prometheus_data: grafana_data: - pnpm_cache: - pnpm_store_server: - pnpm_store_web: + build_cache: server_node_modules: web_node_modules: github_node_modules: diff --git a/docker/docker-compose.prod.yml b/docker/docker-compose.prod.yml index 25751879f3..bad7527ca8 100644 --- a/docker/docker-compose.prod.yml +++ b/docker/docker-compose.prod.yml @@ -56,7 +56,7 @@ services: redis: container_name: immich_redis - image: docker.io/valkey/valkey:9@sha256:3b55fbaa0cd93cf0d9d961f405e4dfcc70efe325e2d84da207a0a8e6d8fde4f9 + image: docker.io/valkey/valkey:9@sha256:4963247afc4cd33c7d3b2d2816b9f7f8eeebab148d29056c2ca4d7cbc966f2d9 healthcheck: test: redis-cli ping || exit 1 restart: always @@ -85,7 +85,7 @@ services: container_name: immich_prometheus ports: - 9090:9090 - image: prom/prometheus@sha256:e4254400b85610324913f0dc4acf92603d9984e7519414c5a12811aa6146acc3 + image: prom/prometheus@sha256:69f5241418838263316593f7274a304b095c40bcf22e57272865da91bd60a8ac volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml - prometheus-data:/prometheus @@ -97,7 +97,7 @@ services: command: ['./run.sh', '-disable-reporting'] ports: - 3000:3000 - image: grafana/grafana:12.4.2-ubuntu@sha256:78839fe49e1425c02416fa8072591533a72bd9598e563b54a07d78f9e27fb5d3 + image: grafana/grafana:12.4.3-ubuntu@sha256:ca3f764fdc48cebdf22dd206f33ecb0795a9a7210eacd1b5c02204aebd78b223 volumes: - grafana-data:/var/lib/grafana diff --git a/docker/docker-compose.rootless.yml b/docker/docker-compose.rootless.yml index c16a623807..33cbc4016a 100644 --- a/docker/docker-compose.rootless.yml +++ b/docker/docker-compose.rootless.yml @@ -61,7 +61,7 @@ services: redis: container_name: immich_redis - image: docker.io/valkey/valkey:9@sha256:3b55fbaa0cd93cf0d9d961f405e4dfcc70efe325e2d84da207a0a8e6d8fde4f9 + image: docker.io/valkey/valkey:9@sha256:4963247afc4cd33c7d3b2d2816b9f7f8eeebab148d29056c2ca4d7cbc966f2d9 user: '1000:1000' security_opt: - no-new-privileges:true @@ -95,6 +95,3 @@ services: restart: always healthcheck: disable: false - -volumes: - model-cache: diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 610b375011..98444d8793 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -49,7 +49,7 @@ services: redis: container_name: immich_redis - image: docker.io/valkey/valkey:9@sha256:3b55fbaa0cd93cf0d9d961f405e4dfcc70efe325e2d84da207a0a8e6d8fde4f9 + image: docker.io/valkey/valkey:9@sha256:4963247afc4cd33c7d3b2d2816b9f7f8eeebab148d29056c2ca4d7cbc966f2d9 healthcheck: test: redis-cli ping || exit 1 restart: always diff --git a/docs/.nvmrc b/docs/.nvmrc deleted file mode 100644 index 5bf4400f22..0000000000 --- a/docs/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -24.15.0 diff --git a/docs/docs/FAQ.mdx b/docs/docs/FAQ.mdx index 7b7a265ddf..1a17385132 100644 --- a/docs/docs/FAQ.mdx +++ b/docs/docs/FAQ.mdx @@ -26,6 +26,8 @@ For organizations seeking to resell Immich, we have established the following gu When in doubt or if you have an edge case scenario, we encourage you to contact us directly via email to discuss the use of our trademark. We can provide clear guidance on what is acceptable and what is not. You can reach out at: questions@immich.app +--- + ## User ### How can I reset the admin password? @@ -36,6 +38,10 @@ The admin password can be reset by running the [reset-admin-password](/administr You can see the list of all users by running [list-users](/administration/server-commands.md) Command on the Immich-server. +### How can I change my profile picture? + +View a single photo, press the three dots in the top-right to show context menu, and select "Set as profile picture". In the pop-up, use your mouse scroll wheel to zoom in the picture until it completely fills the circle. Click and drag the picture to align it to your liking. Press "Save" to save your changes. + --- ## Mobile App diff --git a/docs/docs/administration/server-commands.md b/docs/docs/administration/server-commands.md index cbd029296f..6938cfadd6 100644 --- a/docs/docs/administration/server-commands.md +++ b/docs/docs/administration/server-commands.md @@ -13,8 +13,11 @@ The `immich-server` docker image comes preinstalled with an administrative CLI ( | `enable-oauth-login` | Enable OAuth login | | `disable-oauth-login` | Disable OAuth login | | `list-users` | List Immich users | +| `grant-admin` | Grant admin privileges to a user (by email) | +| `revoke-admin` | Revoke admin privileges from a user (by email) | | `version` | Print Immich version | | `change-media-location` | Change database file paths to align with a new media location | +| `schema-check` | Verify database migrations and check for schema drift | ## How to run a command @@ -102,6 +105,22 @@ immich-admin list-users ] ``` +Grant Admin + +``` +immich-admin grant-admin +? Please enter the user email: user@example.com +Admin access has been granted to user@example.com +``` + +Revoke Admin + +``` +immich-admin revoke-admin +? Please enter the user email: user@example.com +Admin access has been revoked from user@example.com +``` + Print Immich Version ``` @@ -126,3 +145,12 @@ immich-admin change-media-location Database file paths updated successfully! ๐ŸŽ‰ ... ``` + +Schema Check + +``` +immich-admin schema-check +Migrations are up to date + +No schema drift detected +``` diff --git a/docs/docs/api.md b/docs/docs/api.md index edf58dc94d..9336fcf40d 100644 --- a/docs/docs/api.md +++ b/docs/docs/api.md @@ -10,4 +10,4 @@ OpenAPI is used to generate the client (Typescript, Dart) SDK. `openapi-generato make open-api ``` -You can find the generated client SDK in the `open-api/typescript-sdk/client` for Typescript SDK and `mobile/openapi` for Dart SDK. +You can find the generated client SDK in the `packages/sdk/client` for Typescript SDK and `mobile/openapi` for Dart SDK. diff --git a/docs/docs/developer/database-migrations.md b/docs/docs/developer/database-migrations.md index a73e7e747c..05f5941f70 100644 --- a/docs/docs/developer/database-migrations.md +++ b/docs/docs/developer/database-migrations.md @@ -5,7 +5,7 @@ After making any changes in the `server/src/schema`, a database migration need t 1. Run the command ```bash -pnpm run migrations:generate +mise //server:migrations generate ``` 2. Check if the migration file makes sense. @@ -18,7 +18,7 @@ The server will automatically detect `*.ts` file changes and restart. Part of th If you need to undo the most recently applied migrationโ€”for example, when developing or testing on schema changesโ€”run: ```bash -pnpm run migrations:revert +mise //server:migrations revert ``` This command rolls back the latest migration and brings the database schema back to its previous state. diff --git a/docs/docs/developer/devcontainers.md b/docs/docs/developer/devcontainers.md index 4bd60262ad..27004215d5 100644 --- a/docs/docs/developer/devcontainers.md +++ b/docs/docs/developer/devcontainers.md @@ -205,7 +205,7 @@ When the Dev Container starts, it automatically: 1. **Runs post-create script** (`container-server-post-create.sh`): - Adjusts file permissions for the `node` user - Installs dependencies: `pnpm install` in all packages - - Builds TypeScript SDK: `pnpm run build` in `open-api/typescript-sdk` + - Builds TypeScript SDK: `pnpm --filter @immich/sdk build` 2. **Starts development servers** via VS Code tasks: - `Immich API Server (Nest)` - API server with hot-reloading on port 2283 @@ -243,8 +243,8 @@ To connect the mobile app to your Dev Container: - **Server code** (`/server`): Changes trigger automatic restart - **Web code** (`/web`): Changes trigger hot module replacement -- **Database migrations**: Run `pnpm run sync:sql` in the server directory -- **API changes**: Regenerate TypeScript SDK with `make open-api` +- **Database migrations**: Run `mise //:sql` +- **API changes**: Regenerate TypeScript SDK with `mise //:open-api` ## Testing @@ -252,85 +252,33 @@ To connect the mobile app to your Dev Container: The Dev Container supports multiple ways to run tests: -#### Using Make Commands (Recommended) - ```bash -# Run tests for specific components -make test-server # Server unit tests -make test-web # Web unit tests -make test-e2e # End-to-end tests -make test-cli # CLI tests +# Server +mise //server:test # unit tests +mise //server:test-medium # medium / integration tests -# Run all tests -make test-all # Runs tests for all components +# Web +mise //web:test # unit tests -# Medium tests (integration tests) -make test-medium-dev # End-to-end tests +# E2E +mise //e2e:test # API tests +mise //e2e:test-web # web UI tests (Playwright) + +# Run all checks for a component +mise //server:checklist +mise //web:checklist ``` -#### Using PNPM Directly +### Additional Commands ```bash -# Server tests -cd /workspaces/immich/server -pnpm test # Run all tests -pnpm run test:medium # Medium tests (integration tests) -pnpm run test:watch # Watch mode -pnpm run test:cov # Coverage report - -# Web tests -cd /workspaces/immich/web -pnpm test # Run all tests -pnpm run test:watch # Watch mode - -# E2E tests -cd /workspaces/immich/e2e -pnpm run test # Run API tests -pnpm run test:web # Run web UI tests -``` - -### Code Quality Commands - -```bash -# Linting -make lint-server # Lint server code -make lint-web # Lint web code -make lint-all # Lint all components - -# Formatting -make format-server # Format server code -make format-web # Format web code -make format-all # Format all code - -# Type checking -make check-server # Type check server -make check-web # Type check web -make check-all # Check all components - -# Complete hygiene check -make hygiene-all # Run lint, format, check, SQL sync, and audit -``` - -### Additional Make Commands - -```bash -# Build commands -make build-server # Build server -make build-web # Build web app -make build-all # Build everything - # API generation -make open-api # Generate OpenAPI specs -make open-api-typescript # Generate TypeScript SDK -make open-api-dart # Generate Dart SDK +mise //:open-api # Generate OpenAPI specs +mise //:open-api-typescript # Generate TypeScript SDK +mise //:open-api-dart # Generate Dart SDK # Database -make sql # Sync database schema - -# Dependencies -make install-server # Install server dependencies -make install-web # Install web dependencies -make install-all # Install all dependencies +mise //server:sql # Sync database schema ``` ### Debugging diff --git a/docs/docs/developer/directories.md b/docs/docs/developer/directories.md index 409353e2c4..23381946bb 100644 --- a/docs/docs/developer/directories.md +++ b/docs/docs/developer/directories.md @@ -10,7 +10,8 @@ Our [GitHub Repository](https://github.com/immich-app/immich) is a [monorepo](ht | :------------------ | :------------------------------------------------------------------- | | `.github/` | Github templates and action workflows | | `.vscode/` | VSCode debug launch profiles | -| `cli/` | Source code for the work-in-progress CLI rewrite | +| `packages/cli` | Source code for the CLI | +| `packages/sdk` | Source code for the generated OpenAPI SDK | | `docker/` | Docker compose resources for dev, test, production | | `design/` | Screenshots and logos for the README | | `docs/` | Source code for the [https://immich.app](https://immich.app) website | diff --git a/docs/docs/developer/pr-checklist.md b/docs/docs/developer/pr-checklist.md index e5dc6cc1e5..bab9924126 100644 --- a/docs/docs/developer/pr-checklist.md +++ b/docs/docs/developer/pr-checklist.md @@ -8,47 +8,68 @@ When contributing code through a pull request, please check the following: ## Web Checks -- [ ] `pnpm run lint` (linting via ESLint) -- [ ] `pnpm run format` (formatting via Prettier) -- [ ] `pnpm run check:svelte` (Type checking via SvelteKit) -- [ ] `pnpm run check:typescript` (check typescript) -- [ ] `pnpm test` (unit tests) +- [ ] `mise //web:lint` (linting via ESLint) +- [ ] `mise //web:format` (formatting via Prettier) +- [ ] `mise //web:check-svelte` (type checking via SvelteKit) +- [ ] `mise //web:check-typescript` (type checking via `tsc`) +- [ ] `mise //web:test` (unit tests) :::tip AIO -Run all web checks with `pnpm run check:all` +Run all web checks with `mise //web:checklist` +::: + +:::tip Auto Fix +Use `mise //web:lint-fix` and `mise //web:format-fix` to automatically correct some issues. ::: ## Documentation -- [ ] `pnpm run format` (formatting via Prettier) +- [ ] `mise //docs:format` (formatting via Prettier) - [ ] Update the `_redirects` file if you have renamed a page or removed it from the documentation. +:::tip Auto Fix +Use `mise //docs:format-fix` to automatically fix formatting. +::: + ## Server Checks -- [ ] `pnpm run lint` (linting via ESLint) -- [ ] `pnpm run format` (formatting via Prettier) -- [ ] `pnpm run check` (Type checking via `tsc`) -- [ ] `pnpm test` (unit tests) +- [ ] `mise //server:lint` (linting via ESLint) +- [ ] `mise //server:format` (formatting via Prettier) +- [ ] `mise //server:check` (type checking via `tsc`) +- [ ] `mise //server:test` (unit tests) :::tip AIO -Run all server checks with `pnpm run check:all` +Run all server checks with `mise //server:checklist` ::: -:::info Auto Fix -You can use `pnpm run __:fix` to potentially correct some issues automatically for `pnpm run format` and `lint`. +:::tip Auto Fix +Use `mise //server:lint-fix` and `mise //server:format-fix` to automatically correct some issues. ::: -## Mobile Checks +## Mobile Checklist -The following commands must be executed from within the mobile app directory of the codebase. +- [ ] `mise //mobile:codegen` (auto-generate files using build_runner) +- [ ] `mise //mobile:lint` (static analysis via Dart Analyzer and DCM) +- [ ] `mise //mobile:format` (formatting via Dart Formatter) +- [ ] `mise //mobile:test` (unit tests) -- [ ] `make build` (auto-generate files using build_runner) -- [ ] `make analyze` (static analysis via Dart Analyzer and DCM) -- [ ] `make format` (formatting via Dart Formatter) -- [ ] `make test` (unit tests) +:::tip +Run all these commands at once with `mise //mobile:checklist` +::: -:::info Auto Fix -You can use `dart fix --apply` and `dcm fix lib` to potentially correct some issues automatically for `make analyze`. +:::tip Auto Fix +You can use `mise //mobile:lint-fix` to potentially correct some issues automatically for `mise //mobile:lint`. +::: + +## Machine Learning Checklist + +- [ ] `mise //machine-learning:lint` (linting via ruff) +- [ ] `mise //machine-learning:format` (formatting via ruff) +- [ ] `mise //machine-learning:check` (type checking via mypy) +- [ ] `mise //machine-learning:test` (unit tests via pytest) + +:::tip AIO +Run all machine learning checks with `mise //machine-learning:checklist` ::: ## OpenAPI diff --git a/docs/docs/developer/setup.md b/docs/docs/developer/setup.md index abdb3befbe..cac9f501d8 100644 --- a/docs/docs/developer/setup.md +++ b/docs/docs/developer/setup.md @@ -32,6 +32,10 @@ This environment includes the services below. Additional details are available i All the services are packaged to run as with single Docker Compose command. +:::tip mise +[mise](https://mise.jdx.dev) is used throughout the project to manage tool versions and run tasks. [Install mise](https://mise.jdx.dev/installing-mise.html), then from the repo root run `mise trust` and `mise install` to get all required tools. Tasks for each service can be run from the repo root using `mise //namespace:task` (e.g. `mise //server:lint`). To list all available tasks, run `mise tasks ls --all`. +::: + ### Server and web apps 1. Clone the project repo. @@ -56,22 +60,23 @@ You can access the web from `http://your-machine-ip:3000` or `http://localhost:3 #### Connect web to a remote backend -If you only want to do web development connected to an existing, remote backend, follow these steps: - -1. Build the Immich SDK - `cd open-api/typescript-sdk && pnpm i && pnpm run build && cd -` -2. Enter the web directory - `cd web/` -3. Install web dependencies - `pnpm i` -4. Start the web development server +If you only want to do web development connected to an existing, remote backend, run from the repo root: ```bash -IMMICH_SERVER_URL=https://demo.immich.app/ pnpm run dev +IMMICH_SERVER_URL=https://demo.immich.app/ mise //web:start +``` + +This will install all dependencies (including the SDK) and start the dev server in one step. To connect to the hosted demo server specifically, use the shorthand: + +```bash +mise //web:start-demo ``` If you're using PowerShell on Windows you may need to set the env var separately like so: ```powershell $env:IMMICH_SERVER_URL = "https://demo.immich.app/" -pnpm run dev +mise //web:start ``` #### `@immich/ui` @@ -90,24 +95,38 @@ To see local changes to `@immich/ui` in Immich, do the following: #### Setup -1. [Install mise](https://mise.jdx.dev/installing-mise.html). -2. Change to the immich (root) directory and trust the mise config with `mise trust`. -3. Install tools with mise: `mise install`. -4. Change to the `mobile/` directory. -5. Run `flutter pub get` to install the dependencies. -6. Run `make translation` to generate the translation file. -7. Run `flutter run` to start the app. +1. Run `mise //mobile:install` to install Flutter dependencies. +2. Run `mise //mobile:translation` to generate the translation file. +3. Change to the `mobile/` directory and run `flutter run` to start the app. #### Translation -To add a new translation text, enter the key-value pair in the `i18n/en.json` in the root of the immich project. Then, from the `mobile/` directory, run +To add a new translation text, enter the key-value pair in the `i18n/en.json` in the root of the immich project. Then run: ```bash -make translation +mise //mobile:translation ``` The mobile app asks you what backend to connect to. You can utilize the demo backend (https://demo.immich.app/) if you don't need to change server code or upload photos. Alternatively, you can run the server yourself per the instructions above. +#### UI components and widget previews + +Shared design-system widgets (buttons, inputs, forms) live in the +[`immich_ui` package](https://github.com/immich-app/immich/tree/main/mobile/packages/ui/) +under `mobile/packages/ui/`. Components are defined in `lib/src/components/` +and have matching previews in `lib/src/previews/`. + +To inspect a component in isolation with a light/dark toggle and hot reload, +launch [Flutter's Widget Previewer](https://docs.flutter.dev/tools/widget-previewer): + +```bash +cd mobile/packages/ui +flutter widget-preview start +``` + +In VS Code or Android Studio with the Flutter plugin, the previewer +auto-starts when you open the **Flutter Widget Preview** tab in the sidebar. + ## IDE setup ### Lint / format extensions diff --git a/docs/docs/developer/testing.md b/docs/docs/developer/testing.md index d7c9edcd31..7f8ed629e0 100644 --- a/docs/docs/developer/testing.md +++ b/docs/docs/developer/testing.md @@ -4,8 +4,8 @@ ### Unit tests -Unit are run by calling `pnpm run test` from the `server/` directory. -You need to run `pnpm install` (in `server/`) before _once_. +Unit tests are run with `mise //server:test`. +You need to run `mise //server:install` before _once_. ### End to end tests @@ -17,15 +17,13 @@ make e2e Before you can run the tests, you need to run the following commands _once_: -- `pnpm install` (in `e2e/`) -- `pnpm run build` (in `cli/`) -- `make open-api` (in the project root `/`) +- `mise //e2e:ci-setup` (installs e2e, SDK, and CLI dependencies) +- `mise //:open-api` Once the test environment is running, the e2e tests can be run via: ```bash -cd e2e/ -pnpm test +mise //e2e:test ``` The tests check various things including: diff --git a/docs/docs/features/libraries.md b/docs/docs/features/libraries.md index 6e8246b06c..62831ab089 100644 --- a/docs/docs/features/libraries.md +++ b/docs/docs/features/libraries.md @@ -50,6 +50,8 @@ Some basic examples: - `**/Raw/**` will exclude all files in any directory named `Raw` - `**/*.{tif,jpg}` will exclude all files with the extension `.tif` or `.jpg` +Note that `*` is a wildcard matching zero or more characters (i.e., withinin a filename or single directory name). `**` matches zero or more subdirectories, recursively. It also includes any/all files within a subdirectory, i.e., when used at the end of a pattern. For example, `**/exclude_me/**` will exclude all files in any directory named `exclude_me`, as well as all files in any subdirectories of `exclude_me`, recursively. + Special characters such as @ should be escaped, for instance: - `**/\@eaDir/**` will exclude all files in any directory named `@eaDir` diff --git a/docs/docs/features/ml-hardware-acceleration.md b/docs/docs/features/ml-hardware-acceleration.md index bd4fe49e96..5ad0bcd11f 100644 --- a/docs/docs/features/ml-hardware-acceleration.md +++ b/docs/docs/features/ml-hardware-acceleration.md @@ -47,6 +47,7 @@ You do not need to redo any machine learning jobs after enabling hardware accele #### ROCm +- On Linux, The [AMDGPU driver module](https://rocm.docs.amd.com/projects/install-on-linux/en/latest/how-to/docker.html) needs to be installed on the server and, if secure boot is used, the signing key of DKMS [needs to be enrolled in UEFI BIOS](https://wiki.debian.org/SecureBoot) - The GPU must be supported by ROCm. If it isn't officially supported, you can attempt to use the `HSA_OVERRIDE_GFX_VERSION` environmental variable: `HSA_OVERRIDE_GFX_VERSION=`. If this doesn't work, you might need to also set `HSA_USE_SVM=0`. - The ROCm image is quite large and requires at least 35GiB of free disk space. However, pulling later updates to the service through Docker will generally only amount to a few hundred megabytes as the rest will be cached. - This backend is new and may experience some issues. For example, GPU power consumption can be higher than usual after running inference, even if the machine learning service is idle. In this case, it will only go back to normal after being idle for 5 minutes (configurable with the [MACHINE_LEARNING_MODEL_TTL](/install/environment-variables) setting). diff --git a/docs/docs/features/searching.md b/docs/docs/features/searching.md index 92eb01c39d..1bdfeca8ba 100644 --- a/docs/docs/features/searching.md +++ b/docs/docs/features/searching.md @@ -18,6 +18,7 @@ You can search the following types of content: | People | Faces that are recognized in your photos/videos. | | Contextual | Content of the photos and videos. | | File name or extension | Full or partial file's name, or file's extension | +| Full path or folder | Full or partial folder names from the original path. | | Description | Description added to assets. | | Optical Character Recognition (OCR) | Text in images | | Locations | Cities, states, and countries from reverse geocoding. | @@ -30,6 +31,12 @@ You can search the following types of content: +### Full path or folder + +Use this mode when you know a folder name or part of the original asset path. + +Example: for /John/Projects/3D_Printing/2026-07-01/IMG_0001.jpg, searches like Projects, 3D, Printing, or 2026 match the asset. + ## Configuration Navigating to `Administration > Settings > Machine Learning Settings > Smart Search` will show the options available. diff --git a/docs/docs/install/config-file.md b/docs/docs/install/config-file.md index 4754497d90..c8ebeffbcd 100644 --- a/docs/docs/install/config-file.md +++ b/docs/docs/install/config-file.md @@ -26,7 +26,7 @@ The default configuration looks like this: }, "ffmpeg": { "accel": "disabled", - "accelDecode": false, + "accelDecode": true, "acceptedAudioCodecs": ["aac", "mp3", "opus"], "acceptedContainers": ["mov", "ogg", "webm"], "acceptedVideoCodecs": ["h264"], @@ -264,4 +264,4 @@ volumes: - ./configuration.yml:${IMMICH_CONFIG_FILE} ``` -:: +::: diff --git a/docs/docs/install/environment-variables.md b/docs/docs/install/environment-variables.md index 1b67637ac0..0932fa2855 100644 --- a/docs/docs/install/environment-variables.md +++ b/docs/docs/install/environment-variables.md @@ -29,29 +29,31 @@ These environment variables are used by the `docker-compose.yml` file and do **N ## General -| Variable | Description | Default | Containers | Workers | -| :---------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------: | :----------------------- | :----------------- | -| `TZ` | Timezone | \*1 | server | microservices | -| `IMMICH_ENV` | Environment (production, development) | `production` | server, machine learning | api, microservices | -| `IMMICH_LOG_LEVEL` | Log level (verbose, debug, log, warn, error) | `log` | server, machine learning | api, microservices | -| `IMMICH_LOG_FORMAT` | Log output format (`console`, `json`) | `console` | server | api, microservices | -| `IMMICH_MEDIA_LOCATION` | Media location inside the container โš ๏ธ**You probably shouldn't set this**\*2โš ๏ธ | `/data` | server | api, microservices | -| `IMMICH_CONFIG_FILE` | Path to config file | | server | api, microservices | -| `IMMICH_HELMET_FILE` | Path to a json file with [helmet](https://www.npmjs.com/package/helmet) options. Set to `false` to disable. Set to `true` to use `server/helmet.json`. | `false` | server | api | -| `NO_COLOR` | Set to `true` to disable color-coded log output | `false` | server, machine learning | | -| `CPU_CORES` | Number of cores available to the Immich server | auto-detected CPU core count | server | | -| `IMMICH_API_METRICS_PORT` | Port for the OTEL metrics | `8081` | server | api | -| `IMMICH_MICROSERVICES_METRICS_PORT` | Port for the OTEL metrics | `8082` | server | microservices | -| `IMMICH_PROCESS_INVALID_IMAGES` | When `true`, generate thumbnails for invalid images | | server | microservices | -| `IMMICH_TRUSTED_PROXIES` | List of comma-separated IPs set as trusted proxies | | server | api | -| `IMMICH_IGNORE_MOUNT_CHECK_ERRORS` | See [System Integrity](/administration/system-integrity) | | server | api, microservices | -| `IMMICH_ALLOW_SETUP` | When `false` disables the `/auth/admin-sign-up` endpoint | `true` | server | api | +| Variable | Description | Default | Containers | Workers | +| :---------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------: | :----------------------- | :----------------- | +| `TZ` | Timezone | \*1 | server | microservices | +| `IMMICH_ENV` | Environment (production, development) | `production` | server, machine learning | api, microservices | +| `IMMICH_LOG_LEVEL` | Log level (verbose, debug, log, warn, error) | `log` | server, machine learning | api, microservices | +| `IMMICH_LOG_FORMAT` | Log output format (`console`, `json`) | `console` | server | api, microservices | +| `IMMICH_MEDIA_LOCATION` | Media location inside the container โš ๏ธ**You probably shouldn't set this**\*2โš ๏ธ | `/data` | server | api, microservices | +| `IMMICH_CONFIG_FILE` | Path to config file | | server | api, microservices | +| `IMMICH_HELMET_FILE` | Path to a json file with [helmet](https://www.npmjs.com/package/helmet) options. Set to `false` to disable. Set to `true` to use `server/helmet.json`\*3. | `false` | server | api | +| `NO_COLOR` | Set to `true` to disable color-coded log output | `false` | server, machine learning | | +| `CPU_CORES` | Number of cores available to the Immich server | auto-detected CPU core count | server | | +| `IMMICH_API_METRICS_PORT` | Port for the OTEL metrics | `8081` | server | api | +| `IMMICH_MICROSERVICES_METRICS_PORT` | Port for the OTEL metrics | `8082` | server | microservices | +| `IMMICH_PROCESS_INVALID_IMAGES` | When `true`, generate thumbnails for invalid images | | server | microservices | +| `IMMICH_TRUSTED_PROXIES` | List of comma-separated IPs set as trusted proxies | | server | api | +| `IMMICH_IGNORE_MOUNT_CHECK_ERRORS` | See [System Integrity](/administration/system-integrity) | | server | api, microservices | +| `IMMICH_ALLOW_SETUP` | When `false` disables the `/auth/admin-sign-up` endpoint | `true` | server | api | \*1: `TZ` should be set to a `TZ identifier` from [this list][tz-list]. For example, `TZ="Etc/UTC"`. `TZ` is used by `exiftool` as a fallback in case the timezone cannot be determined from the image metadata. It is also used for logfile timestamps and cron job execution. \*2: This path is where the Immich code looks for the files, which is internal to the docker container. Setting it to a path on your host will certainly break things, you should use the `UPLOAD_LOCATION` variable instead. +\*3: The [default configuration](https://helmetjs.github.io/#content-security-policy) sets `upgrade-insecure-requests`, which tells the browser to upgrade all requests to HTTPS. This breaks on HTTP-only deployments. If you cannot use HTTPS, you should use a custom helmet config file with `"upgrade-insecure-requests": null`. + ## Workers | Variable | Description | Default | Containers | @@ -152,33 +154,33 @@ Redis (Sentinel) URL example JSON before encoding: ## Machine Learning -| Variable | Description | Default | Containers | -| :---------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | :-----------------------------: | :--------------- | -| `MACHINE_LEARNING_MODEL_TTL` | Inactivity time (s) before a model is unloaded (disabled if \<= 0) | `300` | machine learning | -| `MACHINE_LEARNING_MODEL_TTL_POLL_S` | Interval (s) between checks for the model TTL (disabled if \<= 0) | `10` | machine learning | -| `MACHINE_LEARNING_CACHE_FOLDER` | Directory where models are downloaded | `/cache` | machine learning | -| `MACHINE_LEARNING_REQUEST_THREADS`\*1 | Thread count of the request thread pool (disabled if \<= 0) | number of CPU cores | machine learning | -| `MACHINE_LEARNING_MODEL_INTER_OP_THREADS` | Number of parallel model operations | `1` | machine learning | -| `MACHINE_LEARNING_MODEL_INTRA_OP_THREADS` | Number of threads for each model operation | `2` | machine learning | -| `MACHINE_LEARNING_WORKERS`\*2 | Number of worker processes to spawn | `1` | machine learning | -| `MACHINE_LEARNING_HTTP_KEEPALIVE_TIMEOUT_S`\*3 | HTTP Keep-alive time in seconds | `2` | machine learning | -| `MACHINE_LEARNING_WORKER_TIMEOUT` | Maximum time (s) of unresponsiveness before a worker is killed | `120` (`300` if using OpenVINO) | machine learning | -| `MACHINE_LEARNING_PRELOAD__CLIP__TEXTUAL` | Comma-separated list of (textual) CLIP model(s) to preload and cache | | machine learning | -| `MACHINE_LEARNING_PRELOAD__CLIP__VISUAL` | Comma-separated list of (visual) CLIP model(s) to preload and cache | | machine learning | -| `MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__RECOGNITION` | Comma-separated list of (recognition) facial recognition model(s) to preload and cache | | machine learning | -| `MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__DETECTION` | Comma-separated list of (detection) facial recognition model(s) to preload and cache | | machine learning | -| `MACHINE_LEARNING_PRELOAD__OCR__RECOGNITION` | Comma-separated list of (recognition) OCR model(s) to preload and cache | | machine learning | -| `MACHINE_LEARNING_PRELOAD__OCR__DETECTION` | Comma-separated list of (detection) OCR model(s) to preload and cache | | machine learning | -| `MACHINE_LEARNING_ANN` | Enable ARM-NN hardware acceleration if supported | `True` | machine learning | -| `MACHINE_LEARNING_ANN_FP16_TURBO` | Execute operations in FP16 precision: increasing speed, reducing precision (applies only to ARM-NN) | `False` | machine learning | -| `MACHINE_LEARNING_ANN_TUNING_LEVEL` | ARM-NN GPU tuning level (1: rapid, 2: normal, 3: exhaustive) | `2` | machine learning | -| `MACHINE_LEARNING_DEVICE_IDS`\*4 | Device IDs to use in multi-GPU environments | `0` | machine learning | -| `MACHINE_LEARNING_MAX_BATCH_SIZE__FACIAL_RECOGNITION` | Set the maximum number of faces that will be processed at once by the facial recognition model | None (`1` if using OpenVINO) | machine learning | -| `MACHINE_LEARNING_MAX_BATCH_SIZE__OCR` | Set the maximum number of boxes that will be processed at once by the OCR model | `6` | machine learning | -| `MACHINE_LEARNING_RKNN` | Enable RKNN hardware acceleration if supported | `True` | machine learning | -| `MACHINE_LEARNING_RKNN_THREADS` | How many threads of RKNN runtime should be spun up while inferencing. | `1` | machine learning | -| `MACHINE_LEARNING_MODEL_ARENA` | Pre-allocates CPU memory to avoid memory fragmentation | true | machine learning | -| `MACHINE_LEARNING_OPENVINO_PRECISION` | If set to FP16, uses half-precision floating-point operations for faster inference with reduced accuracy (one of [`FP16`, `FP32`], applies only to OpenVINO) | `FP32` | machine learning | +| Variable | Description | Default | Containers | +| :---------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------: | :--------------- | +| `MACHINE_LEARNING_MODEL_TTL` | Inactivity time (s) before a model is unloaded (disabled if \<= 0) | `300` | machine learning | +| `MACHINE_LEARNING_MODEL_TTL_POLL_S` | Interval (s) between checks for the model TTL (disabled if \<= 0) | `10` | machine learning | +| `MACHINE_LEARNING_CACHE_FOLDER` | Directory where models are downloaded | `/cache` | machine learning | +| `MACHINE_LEARNING_REQUEST_THREADS`\*1 | Thread count of the request thread pool (disabled if \<= 0) | number of CPU cores | machine learning | +| `MACHINE_LEARNING_MODEL_INTER_OP_THREADS` | Number of parallel model operations | `1` | machine learning | +| `MACHINE_LEARNING_MODEL_INTRA_OP_THREADS` | Number of threads for each model operation | `2` | machine learning | +| `MACHINE_LEARNING_WORKERS`\*2 | Number of worker processes to spawn | `1` | machine learning | +| `MACHINE_LEARNING_HTTP_KEEPALIVE_TIMEOUT_S`\*3 | HTTP Keep-alive time in seconds | `2` | machine learning | +| `MACHINE_LEARNING_WORKER_TIMEOUT` | Maximum time (s) of unresponsiveness before a worker is killed | `300` (`900` if using ROCm) | machine learning | +| `MACHINE_LEARNING_PRELOAD__CLIP__TEXTUAL` | Comma-separated list of (textual) CLIP model(s) to preload and cache | | machine learning | +| `MACHINE_LEARNING_PRELOAD__CLIP__VISUAL` | Comma-separated list of (visual) CLIP model(s) to preload and cache | | machine learning | +| `MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__RECOGNITION` | Comma-separated list of (recognition) facial recognition model(s) to preload and cache | | machine learning | +| `MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__DETECTION` | Comma-separated list of (detection) facial recognition model(s) to preload and cache | | machine learning | +| `MACHINE_LEARNING_PRELOAD__OCR__RECOGNITION` | Comma-separated list of (recognition) OCR model(s) to preload and cache | | machine learning | +| `MACHINE_LEARNING_PRELOAD__OCR__DETECTION` | Comma-separated list of (detection) OCR model(s) to preload and cache | | machine learning | +| `MACHINE_LEARNING_ANN` | Enable ARM-NN hardware acceleration if supported | `True` | machine learning | +| `MACHINE_LEARNING_ANN_FP16_TURBO` | Execute operations in FP16 precision: increasing speed, reducing precision (applies only to ARM-NN) | `False` | machine learning | +| `MACHINE_LEARNING_ANN_TUNING_LEVEL` | ARM-NN GPU tuning level (1: rapid, 2: normal, 3: exhaustive) | `2` | machine learning | +| `MACHINE_LEARNING_DEVICE_IDS`\*4 | Device IDs to use in multi-GPU environments | `0` | machine learning | +| `MACHINE_LEARNING_MAX_BATCH_SIZE__FACIAL_RECOGNITION` | Set the maximum number of faces that will be processed at once by the facial recognition model | None (`1` if using OpenVINO) | machine learning | +| `MACHINE_LEARNING_MAX_BATCH_SIZE__OCR` | Set the maximum number of boxes that will be processed at once by the OCR model | `6` | machine learning | +| `MACHINE_LEARNING_RKNN` | Enable RKNN hardware acceleration if supported | `True` | machine learning | +| `MACHINE_LEARNING_RKNN_THREADS` | How many threads of RKNN runtime should be spun up while inferencing. | `1` | machine learning | +| `MACHINE_LEARNING_MODEL_ARENA` | Pre-allocates CPU memory to avoid memory fragmentation | true | machine learning | +| `MACHINE_LEARNING_OPENVINO_PRECISION` | If set to FP16, uses half-precision floating-point operations for faster inference with reduced accuracy (one of [`FP16`, `FP32`], applies only to OpenVINO) | `FP32` | machine learning | \*1: It is recommended to begin with this parameter when changing the concurrency levels of the machine learning service and then tune the other ones. diff --git a/docs/docs/install/synology.md b/docs/docs/install/synology.md index b86561dbbf..de96886caa 100644 --- a/docs/docs/install/synology.md +++ b/docs/docs/install/synology.md @@ -52,7 +52,7 @@ Scroll to the bottom of the "**Details**" section and find the `IP Address` list ## Step 4 - Configure Firewall Settings -Once your project completes the build process, your containers will start. In order to be able to access Immich from your browser, you need to configure the firewall settings for your Synology NAS. +Once your project completes the build process, your containers will start. In order to be able to access Immich from your browser, you need to configure the firewall settings for your Synology NAS to allow communication between the Immich containers. Open "**Control Panel**" on your Synology NAS, and select "**Security**". Navigate to "**Firewall**" @@ -74,6 +74,7 @@ Read the [Post Installation](/install/post-install.mdx) steps and [upgrade instr
Updating Immich using Container Manager + Check the post installation and upgrade instructions at the links above before proceeding with this section. ## Step 1. Backup @@ -110,7 +111,7 @@ Go to **Project**, select **Action** then **Build**. This will download, unpack, ## Step 5. Update firewall rule -The default behavior is to automatically start the containers once installed. If `immich_server` runs for a few seconds and then stops, it may be because the firewall rule no longer matches the server IP address. +Without a fixed subnet, the default behavior is to automatically start the containers once installed. If `immich_server` runs for a few seconds and then stops, it may be because the firewall rule no longer matches the server IP address. Go to the **Container** section. Click on `immich_server` and scroll down on **General** to find the IP address. ![Container IP](../../static/img/synology-container-ip.png) @@ -123,4 +124,67 @@ In this example, the IP addresses mismatch and the firewall rule needs to be edi ![Edit IP](../../static/img/synology-fw-ipedit.png) +To prevent future firewall issues, you may set a fixed subnet. [See Set Fixed Subnet](#set-fixed-subnet) for instructions. + +
+ +
+ Set Fixed Subnet + +Docker by default assigns dynamic subnets to bridge networks which can change when rebuilding containers and can cause firewall rules to break. To avoid this, define a fixed subnet in your `docker-compose.yml`: + +## Step 1. Determine current subnet + +Go to the **Container** section. Click on `immich_server` and scroll down on **General** to find the IP address. +![Container IP](../../static/img/synology-container-ip.png) + +## Step 2. Add network configuration + +Add the following network configuration at the end of your `docker-compose.yml` file: + +```yaml +networks: + immich-network: + driver: bridge + ipam: + config: + - subnet: 172.20.0.0/16 + gateway: 172.20.0.1 +``` + +If your docker container is running on a different subnet then update accordingly. + +## Step 3. Add network to each service + +Add the network to each service (immich-server, immich-machine-learning, redis, database): + +```yaml +services: + immich-server: + # other config options + networks: + - immich-network + + immich-machine-learning: + # other config options + networks: + - immich-network + + redis: + # other config options + networks: + - immich-network + + database: + # other config options + networks: + - immich-network +``` + +Save your changes. Synology will ask if you want to save changes only or rebuild containers. Select rebuild containers. + +## Step 4. Update Firewall Rules, if necessary + +If your firewall rules were not already set for this subnet, the firewall rules will need to be updated. See [Step 4 - Configure Firewall Settings](#step-4---configure-firewall-settings). +
diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js index 00a120b8b6..734317a302 100644 --- a/docs/docusaurus.config.js +++ b/docs/docusaurus.config.js @@ -10,7 +10,6 @@ const config = { url: 'https://docs.immich.app', baseUrl: '/', onBrokenLinks: 'throw', - onBrokenMarkdownLinks: 'warn', favicon: 'img/favicon.png', // GitHub pages deployment config. @@ -29,6 +28,9 @@ const config = { // Mermaid diagrams markdown: { mermaid: true, + hooks: { + onBrokenMarkdownLinks: 'warn', + }, }, themes: ['@docusaurus/theme-mermaid'], diff --git a/docs/mise.lock b/docs/mise.lock new file mode 100644 index 0000000000..dee476c431 --- /dev/null +++ b/docs/mise.lock @@ -0,0 +1,5 @@ +# @generated - this file is auto-generated by `mise lock` https://mise.en.dev/dev-tools/mise-lock.html + +[[tools.wrangler]] +version = "4.66.0" +backend = "npm:wrangler" diff --git a/docs/mise.toml b/docs/mise.toml index 32fcac5578..165f1f4a93 100644 --- a/docs/mise.toml +++ b/docs/mise.toml @@ -3,7 +3,7 @@ run = "pnpm install --filter documentation --frozen-lockfile" [tasks.start] env._.path = "./node_modules/.bin" -run = "docusaurus --port 3005" +run = "docusaurus start --port 3005" [tasks.build] env._.path = "./node_modules/.bin" @@ -28,4 +28,4 @@ run = "prettier --write ." run = "wrangler pages deploy build --project-name=${PROJECT_NAME} --branch=${BRANCH_NAME}" [tools] -wrangler = "4.66.0" +wrangler = "4.91.0" diff --git a/docs/package.json b/docs/package.json index d469d1ffef..e1d26532db 100644 --- a/docs/package.json +++ b/docs/package.json @@ -56,8 +56,5 @@ }, "engines": { "node": ">=20" - }, - "volta": { - "node": "24.15.0" } } diff --git a/e2e/.nvmrc b/e2e/.nvmrc deleted file mode 100644 index 5bf4400f22..0000000000 --- a/e2e/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -24.15.0 diff --git a/e2e/docker-compose.dev.yml b/e2e/docker-compose.dev.yml index b301ef8441..ac6973a0d6 100644 --- a/e2e/docker-compose.dev.yml +++ b/e2e/docker-compose.dev.yml @@ -83,9 +83,7 @@ volumes: model_cache: prometheus_data: grafana_data: - pnpm_cache: - pnpm_store_server: - pnpm_store_web: + build_cache: server_node_modules: web_node_modules: github_node_modules: diff --git a/e2e/docker-compose.yml b/e2e/docker-compose.yml index c8a3b975d4..8a48cbbdd6 100644 --- a/e2e/docker-compose.yml +++ b/e2e/docker-compose.yml @@ -4,7 +4,7 @@ services: e2e-auth-server: container_name: immich-e2e-auth-server build: - context: ../e2e-auth-server + context: ../packages/e2e-auth-server ports: - 2286:2286 @@ -44,7 +44,7 @@ services: redis: container_name: immich-e2e-redis - image: docker.io/valkey/valkey:9@sha256:3b55fbaa0cd93cf0d9d961f405e4dfcc70efe325e2d84da207a0a8e6d8fde4f9 + image: docker.io/valkey/valkey:9@sha256:4963247afc4cd33c7d3b2d2816b9f7f8eeebab148d29056c2ca4d7cbc966f2d9 healthcheck: test: redis-cli ping || exit 1 diff --git a/e2e/mise.toml b/e2e/mise.toml index c298115e40..b149922564 100644 --- a/e2e/mise.toml +++ b/e2e/mise.toml @@ -1,11 +1,21 @@ [tasks.install] run = "pnpm install --filter immich-e2e --frozen-lockfile" +[tasks.build] +dir = "{{ config_root }}" +run = "docker compose build" + [tasks.test] +depends = ["//e2e:build", "//e2e:ci-setup"] env._.path = "./node_modules/.bin" run = "vitest --run" +[tasks.playwright-install] +env._.path = "./node_modules/.bin" +run = "playwright install" + [tasks."test-web"] +depends = ["//e2e:build", "//e2e:ci-setup", "//e2e:playwright-install"] env._.path = "./node_modules/.bin" run = "playwright test" @@ -27,3 +37,23 @@ run = { task = "lint --fix" } [tasks.check] env._.path = "./node_modules/.bin" run = "tsc --noEmit" + + +[tasks.ci-setup] +depends = [ + "//:sdk:install", + "//:sdk:build", + "//packages/cli:install", + "//packages/cli:build", +] +run = { task = ":install" } + + +[tasks.ci-unit] +depends = ["//:sdk:install", "//:sdk:build"] +run = [ + { task = ":install" }, + { task = ":format" }, + { task = ":lint" }, + { task = ":check" }, +] diff --git a/e2e/package.json b/e2e/package.json index a58c709a6d..77003c03d7 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -32,7 +32,7 @@ "@playwright/test": "^1.44.1", "@socket.io/component-emitter": "^3.1.2", "@types/luxon": "^3.4.2", - "@types/node": "^24.12.2", + "@types/node": "^24.12.4", "@types/pg": "^8.15.1", "@types/pngjs": "^6.0.4", "@types/supertest": "^7.0.0", @@ -56,8 +56,5 @@ "utimes": "^5.2.1", "vite-tsconfig-paths": "^6.1.1", "vitest": "^4.0.0" - }, - "volta": { - "node": "24.15.0" } } diff --git a/e2e/src/specs/maintenance/server/database-backups.e2e-spec.ts b/e2e/src/specs/maintenance/server/database-backups.e2e-spec.ts index b69bd099ed..e3bd98db28 100644 --- a/e2e/src/specs/maintenance/server/database-backups.e2e-spec.ts +++ b/e2e/src/specs/maintenance/server/database-backups.e2e-spec.ts @@ -2,7 +2,7 @@ import { LoginResponseDto, ManualJobName } from '@immich/sdk'; import { errorDto } from 'src/responses'; import { app, utils } from 'src/utils'; import request from 'supertest'; -import { afterAll, beforeAll, describe, expect, it } from 'vitest'; +import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest'; describe('/admin/database-backups', () => { let cookie: string | undefined; @@ -13,6 +13,9 @@ describe('/admin/database-backups', () => { admin = await utils.adminSetup({ onboarding: false, }); + }); + + beforeEach(async () => { await utils.resetBackups(admin.accessToken); }); diff --git a/e2e/src/specs/server/api/album.e2e-spec.ts b/e2e/src/specs/server/api/album.e2e-spec.ts index e1e5178476..55b9c44b70 100644 --- a/e2e/src/specs/server/api/album.e2e-spec.ts +++ b/e2e/src/specs/server/api/album.e2e-spec.ts @@ -146,7 +146,7 @@ describe('/albums', () => { it('should not return shared albums with a deleted owner', async () => { const { status, body } = await request(app) - .get('/albums?shared=true') + .get('/albums?isShared=true') .set('Authorization', `Bearer ${user1.accessToken}`); expect(status).toBe(200); @@ -188,7 +188,7 @@ describe('/albums', () => { it('should return the album collection including owned and shared', async () => { const { status, body } = await request(app).get('/albums').set('Authorization', `Bearer ${user1.accessToken}`); expect(status).toBe(200); - expect(body).toHaveLength(4); + expect(body).toHaveLength(5); expect(body).toEqual( expect.arrayContaining([ expect.objectContaining({ @@ -219,13 +219,20 @@ describe('/albums', () => { ]), shared: false, }), + expect.objectContaining({ + albumName: user2SharedUser, + albumUsers: expect.arrayContaining([ + { role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user2.userId }) }, + ]), + shared: true, + }), ]), ); }); - it('should return the album collection filtered by shared', async () => { + it('should return the album collection filtered by isShared', async () => { const { status, body } = await request(app) - .get('/albums?shared=true') + .get('/albums?isShared=true') .set('Authorization', `Bearer ${user1.accessToken}`); expect(status).toBe(200); expect(body).toHaveLength(4); @@ -263,9 +270,9 @@ describe('/albums', () => { ); }); - it('should return the album collection filtered by NOT shared', async () => { + it('should return the album collection filtered by NOT isShared', async () => { const { status, body } = await request(app) - .get('/albums?shared=false') + .get('/albums?isShared=false') .set('Authorization', `Bearer ${user1.accessToken}`); expect(status).toBe(200); expect(body).toHaveLength(1); @@ -282,6 +289,63 @@ describe('/albums', () => { ); }); + it('should return only owned albums when filtered by isOwned=true', async () => { + const { status, body } = await request(app) + .get('/albums?isOwned=true') + .set('Authorization', `Bearer ${user1.accessToken}`); + expect(status).toBe(200); + expect(body).toHaveLength(4); + expect(body).toEqual( + expect.arrayContaining([ + expect.objectContaining({ albumName: user1SharedEditorUser }), + expect.objectContaining({ albumName: user1SharedViewerUser }), + expect.objectContaining({ albumName: user1SharedLink }), + expect.objectContaining({ albumName: user1NotShared }), + ]), + ); + }); + + it('should return only shared-with-me albums when filtered by isOwned=false', async () => { + const { status, body } = await request(app) + .get('/albums?isOwned=false') + .set('Authorization', `Bearer ${user1.accessToken}`); + expect(status).toBe(200); + expect(body).toHaveLength(1); + expect(body).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + albumName: user2SharedUser, + albumUsers: expect.arrayContaining([ + { role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user2.userId }) }, + ]), + }), + ]), + ); + }); + + it('should return owned shared-out albums when filtered by isOwned=true&ishared=true', async () => { + const { status, body } = await request(app) + .get('/albums?isOwned=true&isShared=true') + .set('Authorization', `Bearer ${user1.accessToken}`); + expect(status).toBe(200); + expect(body).toHaveLength(3); + expect(body).toEqual( + expect.arrayContaining([ + expect.objectContaining({ albumName: user1SharedEditorUser }), + expect.objectContaining({ albumName: user1SharedViewerUser }), + expect.objectContaining({ albumName: user1SharedLink }), + ]), + ); + }); + + it('should return empty list when filtered by isOwned=false&isShared=false', async () => { + const { status, body } = await request(app) + .get('/albums?isOwned=false&isShared=false') + .set('Authorization', `Bearer ${user1.accessToken}`); + expect(status).toBe(200); + expect(body).toHaveLength(0); + }); + it('should return the album collection filtered by assetId', async () => { const { status, body } = await request(app) .get(`/albums?assetId=${user1Asset2.id}`) @@ -290,17 +354,17 @@ describe('/albums', () => { expect(body).toHaveLength(2); }); - it('should return the album collection filtered by assetId and ignores shared=true', async () => { + it('should return the album collection filtered by assetId and ignores isShared=true', async () => { const { status, body } = await request(app) - .get(`/albums?shared=true&assetId=${user1Asset1.id}`) + .get(`/albums?isShared=true&assetId=${user1Asset1.id}`) .set('Authorization', `Bearer ${user1.accessToken}`); expect(status).toBe(200); expect(body).toHaveLength(5); }); - it('should return the album collection filtered by assetId and ignores shared=false', async () => { + it('should return the album collection filtered by assetId and ignores isShared=false', async () => { const { status, body } = await request(app) - .get(`/albums?shared=false&assetId=${user1Asset1.id}`) + .get(`/albums?isShared=false&assetId=${user1Asset1.id}`) .set('Authorization', `Bearer ${user1.accessToken}`); expect(status).toBe(200); expect(body).toHaveLength(5); diff --git a/e2e/src/specs/server/api/asset.e2e-spec.ts b/e2e/src/specs/server/api/asset.e2e-spec.ts index 3fbacd5bf6..010b096c4d 100644 --- a/e2e/src/specs/server/api/asset.e2e-spec.ts +++ b/e2e/src/specs/server/api/asset.e2e-spec.ts @@ -7,7 +7,6 @@ import { getMyUser, LoginResponseDto, SharedLinkType, - updateConfig, } from '@immich/sdk'; import { exiftool } from 'exiftool-vendored'; import { DateTime } from 'luxon'; @@ -24,7 +23,6 @@ import { afterAll, beforeAll, describe, expect, it } from 'vitest'; const locationAssetFilepath = `${testAssetDir}/metadata/gps-position/thompson-springs.jpg`; const ratingAssetFilepath = `${testAssetDir}/metadata/rating/mongolels.jpg`; -const facesAssetDir = `${testAssetDir}/metadata/faces`; const readTags = async (bytes: Buffer, filename: string) => { const filepath = join(tempDir, filename); @@ -185,78 +183,6 @@ describe('/asset', () => { }); }); - describe('faces', () => { - const metadataFaceTests = [ - { - description: 'without orientation', - filename: 'portrait.jpg', - }, - { - description: 'adjusting face regions to orientation', - filename: 'portrait-orientation-6.jpg', - }, - ]; - // should produce same resulting face region coordinates for any orientation - const expectedFaces = [ - { - name: 'Marie Curie', - birthDate: null, - isHidden: false, - faces: [ - { - imageHeight: 700, - imageWidth: 840, - boundingBoxX1: 261, - boundingBoxX2: 356, - boundingBoxY1: 146, - boundingBoxY2: 284, - sourceType: 'exif', - }, - ], - }, - { - name: 'Pierre Curie', - birthDate: null, - isHidden: false, - faces: [ - { - imageHeight: 700, - imageWidth: 840, - boundingBoxX1: 536, - boundingBoxX2: 618, - boundingBoxY1: 83, - boundingBoxY2: 252, - sourceType: 'exif', - }, - ], - }, - ]; - - it.each(metadataFaceTests)('should get the asset faces from $filename $description', async ({ filename }) => { - const config = await utils.getSystemConfig(admin.accessToken); - config.metadata.faces.import = true; - await updateConfig({ systemConfigDto: config }, { headers: asBearerAuth(admin.accessToken) }); - - const facesAsset = await utils.createAsset(admin.accessToken, { - assetData: { - filename, - bytes: await readFile(`${facesAssetDir}/${filename}`), - }, - }); - - await utils.waitForWebsocketEvent({ event: 'assetUpload', id: facesAsset.id }); - - const { status, body } = await request(app) - .get(`/assets/${facesAsset.id}`) - .set('Authorization', `Bearer ${admin.accessToken}`); - - expect(status).toBe(200); - expect(body.id).toEqual(facesAsset.id); - const sortedPeople = body.people.toSorted((a: any, b: any) => a.name.localeCompare(b.name)); - expect(sortedPeople).toMatchObject(expectedFaces); - }); - }); - it('should work with a shared link', async () => { const sharedLink = await utils.createSharedLink(user1.accessToken, { type: SharedLinkType.Individual, diff --git a/e2e/src/specs/server/api/search.e2e-spec.ts b/e2e/src/specs/server/api/search.e2e-spec.ts index e3e17f67c2..0b86053f78 100644 --- a/e2e/src/specs/server/api/search.e2e-spec.ts +++ b/e2e/src/specs/server/api/search.e2e-spec.ts @@ -259,17 +259,6 @@ describe('/search', () => { assets: [assetHeic], }), }, - { - should: "should search city ('')", - deferred: () => ({ - dto: { - city: '', - visibility: AssetVisibility.Timeline, - includeNull: true, - }, - assets: [assetLast], - }), - }, { should: 'should search city (null)', deferred: () => ({ @@ -291,18 +280,6 @@ describe('/search', () => { assets: [assetDensity], }), }, - { - should: "should search state ('')", - deferred: () => ({ - dto: { - state: '', - visibility: AssetVisibility.Timeline, - withExif: true, - includeNull: true, - }, - assets: [assetLast, assetNotocactus], - }), - }, { should: 'should search state (null)', deferred: () => ({ @@ -324,17 +301,6 @@ describe('/search', () => { assets: [assetFalcon], }), }, - { - should: "should search country ('')", - deferred: () => ({ - dto: { - country: '', - visibility: AssetVisibility.Timeline, - includeNull: true, - }, - assets: [assetLast], - }), - }, { should: 'should search country (null)', deferred: () => ({ @@ -441,7 +407,18 @@ describe('/search', () => { .get('/search/explore') .set('Authorization', `Bearer ${admin.accessToken}`); expect(status).toBe(200); - expect(body).toEqual([{ fieldName: 'exifInfo.city', items: [] }]); + expect(Array.isArray(body)).toBe(true); + expect(body).toEqual(expect.arrayContaining([{ fieldName: 'exifInfo.city', items: [] }])); + expect(body).toEqual( + expect.arrayContaining([ + { + fieldName: 'createdAt', + items: expect.arrayContaining([ + expect.objectContaining({ data: expect.objectContaining({ id: assetLast.id }) }), + ]), + }, + ]), + ); }); }); diff --git a/e2e/src/specs/server/api/server.e2e-spec.ts b/e2e/src/specs/server/api/server.e2e-spec.ts index 1220e6cab5..902c5302e5 100644 --- a/e2e/src/specs/server/api/server.e2e-spec.ts +++ b/e2e/src/specs/server/api/server.e2e-spec.ts @@ -95,6 +95,7 @@ describe('/server', () => { major: expect.any(Number), minor: expect.any(Number), patch: expect.any(Number), + prerelease: null, }); }); }); @@ -115,6 +116,7 @@ describe('/server', () => { oauthAutoLaunch: false, ocr: false, passwordLogin: true, + realtimeTranscoding: false, search: true, sidecar: true, trash: true, @@ -139,6 +141,7 @@ describe('/server', () => { maintenanceMode: false, mapDarkStyleUrl: 'https://tiles.immich.cloud/v1/style/dark.json', mapLightStyleUrl: 'https://tiles.immich.cloud/v1/style/light.json', + minFaces: 3, }); }); }); diff --git a/e2e/src/specs/server/api/system-config.e2e-spec.ts b/e2e/src/specs/server/api/system-config.e2e-spec.ts index 1bd7bdc489..91b747cf28 100644 --- a/e2e/src/specs/server/api/system-config.e2e-spec.ts +++ b/e2e/src/specs/server/api/system-config.e2e-spec.ts @@ -21,18 +21,18 @@ describe('/system-config', () => { const response1 = await request(app) .put('/system-config') .set('Authorization', `Bearer ${admin.accessToken}`) - .send({ ...config, newVersionCheck: { enabled: false } }); + .send({ ...config, newVersionCheck: { enabled: false, channel: 'stable' } }); expect(response1.status).toBe(200); - expect(response1.body).toEqual({ ...config, newVersionCheck: { enabled: false } }); + expect(response1.body).toEqual({ ...config, newVersionCheck: { enabled: false, channel: 'stable' } }); const response2 = await request(app) .put('/system-config') .set('Authorization', `Bearer ${admin.accessToken}`) - .send({ ...config, newVersionCheck: { enabled: true } }); + .send({ ...config, newVersionCheck: { enabled: true, channel: 'stable' } }); expect(response2.status).toBe(200); - expect(response2.body).toEqual({ ...config, newVersionCheck: { enabled: true } }); + expect(response2.body).toEqual({ ...config, newVersionCheck: { enabled: true, channel: 'stable' } }); }); it('should reject an invalid config entry', async () => { diff --git a/e2e/src/specs/server/api/user.e2e-spec.ts b/e2e/src/specs/server/api/user.e2e-spec.ts index 8a2197efde..2dc789a91b 100644 --- a/e2e/src/specs/server/api/user.e2e-spec.ts +++ b/e2e/src/specs/server/api/user.e2e-spec.ts @@ -230,6 +230,21 @@ describe('/users', () => { const after = await getMyPreferences({ headers: asBearerAuth(admin.accessToken) }); expect(after).toMatchObject({ download: { includeEmbeddedVideos: true } }); }); + + it('should update minimum face count to display people', async () => { + const before = await getMyPreferences({ headers: asBearerAuth(admin.accessToken) }); + expect(before).toMatchObject({ people: { minimumFaces: 3 } }); + + const { status, body } = await request(app) + .put('/users/me/preferences') + .send({ people: { minimumFaces: 2 } }) + .set('Authorization', `Bearer ${admin.accessToken}`); + expect(status).toBe(200); + expect(body).toMatchObject({ people: { minimumFaces: 2 } }); + + const after = await getMyPreferences({ headers: asBearerAuth(admin.accessToken) }); + expect(after).toMatchObject({ people: { minimumFaces: 2 } }); + }); }); describe('GET /users/:id', () => { diff --git a/e2e/src/specs/server/cli/version.e2e-spec.ts b/e2e/src/specs/server/cli/version.e2e-spec.ts index 56a0d8b0b1..de03fdf358 100644 --- a/e2e/src/specs/server/cli/version.e2e-spec.ts +++ b/e2e/src/specs/server/cli/version.e2e-spec.ts @@ -2,7 +2,7 @@ import { readFileSync } from 'node:fs'; import { immichCli } from 'src/utils'; import { describe, expect, it } from 'vitest'; -const pkg = JSON.parse(readFileSync('../cli/package.json', 'utf8')); +const pkg = JSON.parse(readFileSync('../packages/cli/package.json', 'utf8')); describe(`immich --version`, () => { describe('immich --version', () => { diff --git a/e2e/src/ui/generators/timeline/rest-response.ts b/e2e/src/ui/generators/timeline/rest-response.ts index 83a60556be..553ce18005 100644 --- a/e2e/src/ui/generators/timeline/rest-response.ts +++ b/e2e/src/ui/generators/timeline/rest-response.ts @@ -28,6 +28,7 @@ export function toColumnarFormat(assets: MockTimelineAsset[]): TimeBucketAssetRe ownerId: [], ratio: [], thumbhash: [], + createdAt: [], fileCreatedAt: [], localOffsetHours: [], isFavorite: [], @@ -54,8 +55,8 @@ export function toColumnarFormat(assets: MockTimelineAsset[]): TimeBucketAssetRe result.duration.push(asset.duration); result.projectionType.push(asset.projectionType); result.livePhotoVideoId.push(asset.livePhotoVideoId); - result.city.push(asset.city); - result.country.push(asset.country); + result.city?.push(asset.city); + result.country?.push(asset.country); result.visibility.push(asset.visibility); } @@ -338,7 +339,6 @@ export function toAssetResponseDto(asset: MockTimelineAsset, owner?: UserRespons livePhotoVideoId: asset.livePhotoVideoId, tags: [], people: [], - unassignedFaces: [], stack: asset.stack, isOffline: false, hasMetadata: true, diff --git a/e2e/src/ui/mock-network/base-network.ts b/e2e/src/ui/mock-network/base-network.ts index 3dc3580396..6680b83dd1 100644 --- a/e2e/src/ui/mock-network/base-network.ts +++ b/e2e/src/ui/mock-network/base-network.ts @@ -240,7 +240,8 @@ export const setupBaseMockApiRoutes = async (context: BrowserContext, adminUserI }); }); await context.route('**/api/albums*', async (route, request) => { - if (request.url().endsWith('albums?shared=true') || request.url().endsWith('albums')) { + const url = request.url(); + if (url.endsWith('albums?isShared=true') || url.endsWith('albums?isOwned=true') || url.endsWith('albums')) { return route.fulfill({ status: 200, contentType: 'application/json', diff --git a/e2e/src/ui/mock-network/broken-asset-network.ts b/e2e/src/ui/mock-network/broken-asset-network.ts index ce66412e61..2137cdd90f 100644 --- a/e2e/src/ui/mock-network/broken-asset-network.ts +++ b/e2e/src/ui/mock-network/broken-asset-network.ts @@ -66,7 +66,6 @@ export const createMockStackAsset = (ownerId: string): AssetResponseDto => { livePhotoVideoId: null, tags: [], people: [], - unassignedFaces: [], stack: undefined, isOffline: false, hasMetadata: true, diff --git a/e2e/src/ui/specs/timeline/timeline.e2e-spec.ts b/e2e/src/ui/specs/timeline/timeline.e2e-spec.ts index c2a3b8e724..6f986df84f 100644 --- a/e2e/src/ui/specs/timeline/timeline.e2e-spec.ts +++ b/e2e/src/ui/specs/timeline/timeline.e2e-spec.ts @@ -536,7 +536,7 @@ test.describe('Timeline', () => { force: false, ids: [assetToTrash.id], }); - await page.locator('#asset-selection-app-bar').getByLabel('Close').click(); + await page.locator('#control-bar').getByLabel('Close').click(); await page.getByText('Trash', { exact: true }).click(); await timelineUtils.waitForTimelineLoad(page); await thumbnailUtils.expectInViewport(page, assetToTrash.id); @@ -676,7 +676,7 @@ test.describe('Timeline', () => { ids: [assetToArchive.id], }); await thumbnailUtils.expectThumbnailIsArchive(page, assetToArchive.id); - await page.locator('#asset-selection-app-bar').getByLabel('Close').click(); + await page.locator('#control-bar').getByLabel('Close').click(); await page.getByRole('link').getByText('Archive').click(); await timelineUtils.waitForTimelineLoad(page); await thumbnailUtils.expectInViewport(page, assetToArchive.id); @@ -823,7 +823,7 @@ test.describe('Timeline', () => { }); // ensure thumbnail still exists and has favorite icon await thumbnailUtils.expectThumbnailIsFavorite(page, assetToFavorite.id); - await page.locator('#asset-selection-app-bar').getByLabel('Close').click(); + await page.locator('#control-bar').getByLabel('Close').click(); await page.getByRole('link').getByText('Favorites').click(); await timelineUtils.waitForTimelineLoad(page); await pageUtils.goToAsset(page, assetToFavorite.fileCreatedAt); diff --git a/e2e/src/utils.ts b/e2e/src/utils.ts index aa4c3b8499..7e51b40f63 100644 --- a/e2e/src/utils.ts +++ b/e2e/src/utils.ts @@ -90,7 +90,7 @@ export const tempDir = tmpdir(); export const asBearerAuth = (accessToken: string) => ({ Authorization: `Bearer ${accessToken}` }); export const asKeyAuth = (key: string) => ({ 'x-api-key': key }); export const immichCli = (args: string[]) => - executeCommand('pnpm', ['exec', 'immich', '-d', `/${tempDir}/immich/`, ...args], { cwd: '../cli' }).promise; + executeCommand('pnpm', ['exec', 'immich', '-d', `/${tempDir}/immich/`, ...args], { cwd: '../packages/cli' }).promise; export const dockerExec = (args: string[]) => executeCommand('docker', ['exec', '-i', 'immich-e2e-server', '/bin/bash', '-c', args.join(' ')]); export const immichAdmin = (args: string[]) => dockerExec([`immich-admin ${args.join(' ')}`]); @@ -568,6 +568,8 @@ export const utils = { name: ManualJobName.BackupDatabase, }); + await utils.waitForQueueFinish(accessToken, 'backupDatabase'); + return utils.poll( () => request(app).get('/admin/database-backups').set('Authorization', `Bearer ${accessToken}`), ({ status, body }) => status === 200 && body.backups.length === 1, diff --git a/i18n/en.json b/i18n/en.json index cc30d9e350..f4ad3001c2 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -22,13 +22,12 @@ "add_birthday": "Add a birthday", "add_endpoint": "Add endpoint", "add_exclusion_pattern": "Add exclusion pattern", - "add_filter": "Add filter", - "add_filter_description": "Click to add a filter condition", "add_location": "Add location", "add_more_users": "Add more users", "add_partner": "Add partner", "add_path": "Add path", "add_photos": "Add photos", + "add_step": "Add step", "add_tag": "Add tag", "add_to": "Add toโ€ฆ", "add_to_album": "Add to album", @@ -42,7 +41,6 @@ "add_to_shared_album": "Add to shared album", "add_upload_to_stack": "Add upload to stack", "add_url": "Add URL", - "add_workflow_step": "Add workflow step", "added_to_archive": "Added to archive", "added_to_favorites": "Added to favorites", "added_to_favorites_count": "Added {count, number} to favorites", @@ -307,6 +305,8 @@ "refreshing_all_libraries": "Refreshing all libraries", "registration": "Admin Registration", "registration_description": "Since you are the first user on the system, you will be assigned as the Admin and are responsible for administrative tasks, and additional users will be created by you.", + "release_channel_release_candidate": "Release candidate", + "release_channel_stable": "Stable", "remove_failed_jobs": "Remove failed jobs", "require_password_change_on_login": "Require user to change password on first login", "reset_settings_to_default": "Reset settings to default", @@ -401,6 +401,10 @@ "transcoding_preferred_hardware_device_description": "Applies only to VAAPI and QSV. Sets the dri node used for hardware transcoding.", "transcoding_preset_preset": "Preset (-preset)", "transcoding_preset_preset_description": "Compression speed. Slower presets produce smaller files, and increase quality when targeting a certain bitrate. VP9 ignores speeds above 'faster'.", + "transcoding_realtime": "Real-time Transcoding [EXPERIMENTAL]", + "transcoding_realtime_description": "Allows transcoding to be performed in real-time as the video is being streamed. Enables quality switching, but may cause higher playback latency and stuttering depending on server capabilities.", + "transcoding_realtime_enabled": "Enable real-time transcoding", + "transcoding_realtime_enabled_description": "If disabled, the server will refuse to start new real-time transcoding sessions.", "transcoding_reference_frames": "Reference frames", "transcoding_reference_frames_description": "The number of frames to reference when compressing a given frame. Higher values improve compression efficiency, but slow down encoding. 0 sets this value automatically.", "transcoding_required_description": "Only videos not in an accepted format", @@ -444,6 +448,8 @@ "user_settings_description": "Manage user settings", "user_successfully_removed": "User {email} has been successfully removed.", "users_page_description": "Admin users page", + "version_check_channel": "Release channel", + "version_check_channel_description": "Pick the release channel you want to get version announcements for", "version_check_enabled_description": "Enable version check", "version_check_implications": "The version check feature relies on periodic communication with {server}", "version_check_settings": "Version Check", @@ -700,6 +706,7 @@ "birthdate_saved": "Date of birth saved successfully", "birthdate_set_description": "Date of birth is used to calculate the age of this person at the time of a photo.", "blurred_background": "Blurred background", + "browse_templates": "Browse templates", "bugs_and_feature_requests": "Bugs & Feature Requests", "build": "Build", "build_image": "Build Image", @@ -733,6 +740,7 @@ "cannot_update_the_description": "Cannot update the description", "cast": "Cast", "cast_description": "Configure available cast destinations", + "change": "Change", "change_date": "Change date", "change_description": "Change description", "change_display_order": "Change display order", @@ -761,6 +769,7 @@ "check_corrupt_asset_backup_description": "Run this check only over Wi-Fi and once all assets have been backed-up. The procedure might take a few minutes.", "check_logs": "Check Logs", "checksum": "Checksum", + "choose": "Choose", "choose_matching_people_to_merge": "Choose matching people to merge", "city": "City", "cleanup_confirm_description": "Immich found {count} assets (created before {date}) safely backed up to the server. Remove the local copies from this device?", @@ -778,6 +787,7 @@ "clear": "Clear", "clear_all": "Clear all", "clear_all_recent_searches": "Clear all recent searches", + "clear_failed_count": "Clear failed ({count})", "clear_file_cache": "Clear File Cache", "clear_message": "Clear message", "clear_value": "Clear value", @@ -809,6 +819,7 @@ "comments_are_disabled": "Comments are disabled", "common_create_new_album": "Create new album", "completed": "Completed", + "configuration": "Configuration", "confirm": "Confirm", "confirm_admin_password": "Confirm Admin Password", "confirm_delete_face": "Are you sure you want to delete {name} face from the asset?", @@ -823,6 +834,7 @@ "contain": "Contain", "context": "Context", "continue": "Continue", + "control_bottom_app_bar_add_tags": "Add Tags", "control_bottom_app_bar_create_new_album": "Create new album", "control_bottom_app_bar_delete_from_immich": "Delete from Immich", "control_bottom_app_bar_delete_from_local": "Delete from device", @@ -836,6 +848,7 @@ "copy_error": "Copy error", "copy_file_path": "Copy file path", "copy_image": "Copy Image", + "copy_json": "Copy JSON", "copy_link": "Copy link", "copy_link_to_clipboard": "Copy link to clipboard", "copy_password": "Copy password", @@ -885,17 +898,16 @@ "cutoff_date_description": "Keep photos from the lastโ€ฆ", "cutoff_day": "{count, plural, one {day} other {days}}", "cutoff_year": "{count, plural, one {year} other {years}}", - "daily_title_text_date": "E, MMM dd", - "daily_title_text_date_year": "E, MMM dd, yyyy", "dark": "Dark", "dark_theme": "Switch to dark theme", "date": "Date", "date_after": "Date after", "date_and_time": "Date and Time", "date_before": "Date before", - "date_format": "E, LLL d, y โ€ข h:mm a", + "date_of_birth": "Date of birth", "date_of_birth_saved": "Date of birth saved successfully", "date_range": "Date range", + "date_time_original": "Date/Time Original", "day": "Day", "days": "Days", "deduplicate_all": "Deduplicate All", @@ -974,7 +986,10 @@ "downloading_asset_filename": "Downloading asset {filename}", "downloading_from_icloud": "Downloading from iCloud", "downloading_media": "Downloading media", + "drag_to_reorder": "Drag to reorder", "drop_files_to_upload": "Drop files anywhere to upload", + "duplicate": "Duplicate", + "duplicate_workflow": "Duplicate workflow", "duplicates": "Duplicates", "duplicates_description": "Resolve each group by indicating which, if any, are duplicates.", "duration": "Duration", @@ -1076,6 +1091,7 @@ "failed_to_remove_product_key": "Failed to remove product key", "failed_to_reset_pin_code": "Failed to reset PIN code", "failed_to_stack_assets": "Failed to stack assets", + "failed_to_tag_assets": "Failed to tag assets", "failed_to_unstack_assets": "Failed to un-stack assets", "failed_to_update_notification_status": "Failed to update notification status", "incorrect_email_or_password": "Incorrect email or password", @@ -1195,11 +1211,13 @@ "export_as_json": "Export as JSON", "export_database": "Export Database", "export_database_description": "Export the SQLite database", + "exposure_time": "Exposure Time", "extension": "Extension", "external": "External", "external_libraries": "External Libraries", "external_network": "External network", "external_network_sheet_info": "When not on the preferred Wi-Fi network, the app will connect to the server through the first of the below URLs it can reach, starting from top to bottom", + "f_number": "F-Number", "face_unassigned": "Unassigned", "failed": "Failed", "failed_count": "Failed: {count}", @@ -1217,7 +1235,6 @@ "features_setting_description": "Manage the app features", "file_name_or_extension": "File name or extension", "file_name_text": "File name", - "file_name_with_value": "File name: {file_name}", "file_size": "File size", "filename": "Filename", "filetype": "Filetype", @@ -1230,6 +1247,7 @@ "find_them_fast": "Find them fast by name with search", "first": "First", "fix_incorrect_match": "Fix incorrect match", + "focal_length": "Focal Length", "folder": "Folder", "folder_not_found": "Folder not found", "folders": "Folders", @@ -1240,6 +1258,7 @@ "free_up_space_description": "Move backed-up photos and videos to your device's trash to free up space. Your copies on the server remain safe.", "free_up_space_settings_subtitle": "Free up device storage", "full_path": "Full path: {path}", + "full_path_or_folder": "Full path or folder", "gcast_enabled": "Google Cast", "gcast_enabled_description": "This feature loads external resources from Google in order to work.", "general": "General", @@ -1349,6 +1368,7 @@ "ios_debug_info_no_sync_yet": "No background sync job has run yet", "ios_debug_info_processes_queued": "{count, plural, one {{count} background process queued} other {{count} background processes queued}}", "ios_debug_info_processing_ran_at": "Processing ran {dateTime}", + "iso": "ISO", "items_count": "{count, plural, one {# item} other {# items}}", "jobs": "Jobs", "json_editor": "JSON editor", @@ -1402,6 +1422,7 @@ "link_to_oauth": "Link to OAuth", "linked_oauth_account": "Linked OAuth account", "list": "List", + "live": "Live", "loading": "Loading", "loading_search_results_failed": "Loading search results failed", "local": "Local", @@ -1523,6 +1544,38 @@ "marked_all_as_read": "Marked all as read", "matches": "Matches", "matching_assets": "Matching Assets", + "media_chrome": { + "auto": "Auto", + "captions": "Captions", + "captions_off": "Off", + "closed_captions": "closed captions", + "decode_error": "Decode error", + "disable_captions": "Disable captions", + "enable_captions": "Enable captions", + "enter_fullscreen_mode": "Enter fullscreen mode", + "exit_fullscreen_mode": "Exit fullscreen mode", + "loop": "Loop", + "media_error_description": "A media error caused playback to be aborted. The media could be corrupt or your browser does not support this format.", + "media_loading": "media loading", + "mute": "Mute", + "network_error": "Network error", + "network_error_description": "A network error caused the media download to fail.", + "not_supported_error": "Source Not Supported", + "playback_rate": "Playback rate", + "playback_rate_current": "current playback rate", + "playback_rate_value": "Playback rate {playbackRate}", + "playback_time": "playback time", + "quality": "Quality", + "second": "second", + "seconds": "seconds", + "time_value_of_total_time": "{currentTime} of {totalTime}", + "time_value_remaining": "{time} remaining", + "unmute": "Unmute", + "unsupported_error_description": "An unsupported error occurred. The server or network failed, or your browser does not support this format.", + "video_not_loaded_unknown_time": "video not loaded, unknown time.", + "video_player": "video player", + "volume": "volume" + }, "media_type": "Media type", "memories": "Memories", "memories_all_caught_up": "All caught up", @@ -1539,6 +1592,8 @@ "merge_people_prompt": "Do you want to merge these people? This action is irreversible.", "merge_people_successfully": "Merge people successfully", "merged_people_count": "Merged {count, plural, one {# person} other {# people}}", + "minFaces": "Minimum faces", + "minFaces_description": "The minimum number of recognized faces for a person to be displayed", "minimize": "Minimize", "minute": "Minute", "minutes": "Minutes", @@ -1548,9 +1603,10 @@ "mobile_app": "Mobile App", "mobile_app_download_onboarding_note": "Download the companion mobile app using the following options", "model": "Model", + "modify_date": "Modify Date", "month": "Month", - "monthly_title_text_date_format": "MMMM y", "more": "More", + "motion": "Motion", "move": "Move", "move_down": "Move down", "move_off_locked_folder": "Move out of locked folder", @@ -1596,7 +1652,6 @@ "next": "Next", "next_memory": "Next memory", "no": "No", - "no_actions_added": "No actions added yet", "no_albums_found": "No albums found", "no_albums_message": "Create an album to organize your photos and videos", "no_albums_with_name_yet": "It looks like you do not have any albums with this name yet.", @@ -1613,7 +1668,6 @@ "no_exif_info_available": "No exif info available", "no_explore_results_message": "Upload more photos to explore your collection.", "no_favorites_message": "Add favorites to quickly find your best pictures and videos", - "no_filters_added": "No filters added yet", "no_libraries_message": "Create an external library to view your photos and videos", "no_local_assets_found": "No local assets found with this checksum", "no_location_set": "No location set", @@ -1626,6 +1680,7 @@ "no_results": "No results", "no_results_description": "Try a synonym or more general keyword", "no_shared_albums_message": "Create an album to share photos and videos with people in your network", + "no_steps": "No steps added yet", "no_uploads_in_progress": "No uploads in progress", "none": "None", "not_allowed": "Not allowed", @@ -1671,6 +1726,7 @@ "organize_into_albums": "Organize into albums", "organize_into_albums_description": "Put existing photos into albums using current sync settings", "organize_your_library": "Organize your library", + "orientation": "Orientation", "original": "original", "other": "Other", "other_devices": "Other devices", @@ -1761,8 +1817,9 @@ "play_original_video": "Play original video", "play_original_video_setting_description": "Prefer playback of original videos rather than transcoded videos. If original asset is not compatible it may not playback correctly.", "play_transcoded_video": "Play transcoded video", - "playback_speed": "Playback speed", "please_auth_to_access": "Please authenticate to access", + "plugin_method_filter_type": "Filter", + "plugin_method_filter_type_description": "This method can filter events and conditionally prevent subsequent steps from running", "port": "Port", "preferences_settings_subtitle": "Manage the app's preferences", "preferences_settings_title": "Preferences", @@ -1784,6 +1841,7 @@ "profile_drawer_readonly_mode": "Read-only mode enabled. Long-press the user avatar icon to exit.", "profile_image_of_user": "Profile image of {user}", "profile_picture_set": "Profile picture set.", + "projection_type": "Projection Type", "public_album": "Public album", "public_share": "Public Share", "purchase_account_info": "Supporter", @@ -1861,6 +1919,7 @@ "remove_assets_title": "Remove assets?", "remove_custom_date_range": "Remove custom date range", "remove_deleted_assets": "Remove Deleted Assets", + "remove_filter": "Remove filter", "remove_from_album": "Remove from album", "remove_from_album_action_prompt": "{count} removed from the album", "remove_from_favorites": "Remove from favorites", @@ -1943,6 +2002,8 @@ "search_by_description_example": "Hiking day in Sapa", "search_by_filename": "Search by file name or extension", "search_by_filename_example": "i.e. IMG_1234.JPG or PNG", + "search_by_full_path": "Search by full path or folder", + "search_by_full_path_example": "/John/Projects/3D_Printing/2026-07-01 - you can search for Projects, 3D, Printing, 2026 etc.", "search_by_ocr": "Search by OCR", "search_by_ocr_example": "Latte", "search_camera_lens_model": "Search lens model...", @@ -2150,7 +2211,9 @@ "show_in_timeline": "Show in timeline", "show_in_timeline_setting_description": "Show photos and videos from this user in your timeline", "show_keyboard_shortcuts": "Show keyboard shortcuts", + "show_less": "Show less", "show_metadata": "Show metadata", + "show_more_fields": "{count, plural, one {Show # more field} other {Show # more fields}}", "show_or_hide_info": "Show or hide info", "show_password": "Show password", "show_person_options": "Show person options", @@ -2158,6 +2221,7 @@ "show_schema": "Show schema", "show_search_options": "Show search options", "show_shared_links": "Show shared links", + "show_slideshow_metadata_overlay": "Show image info overlay", "show_slideshow_transition": "Show slideshow transition", "show_supporter_badge": "Supporter badge", "show_supporter_badge_description": "Show a supporter badge", @@ -2173,9 +2237,13 @@ "skip_to_folders": "Skip to folders", "skip_to_tags": "Skip to tags", "slideshow": "Slideshow", + "slideshow_metadata_overlay_mode": "Overlay content", + "slideshow_metadata_overlay_mode_description_only": "Description only", + "slideshow_metadata_overlay_mode_full": "Full", "slideshow_repeat": "Repeat slideshow", "slideshow_repeat_description": "Loop back to beginning when slideshow ends", "slideshow_settings": "Slideshow settings", + "smart_album": "Smart album", "sort_albums_by": "Sort albums by...", "sort_created": "Date created", "sort_items": "Number of items", @@ -2198,6 +2266,11 @@ "start_date_before_end_date": "Start date must be before end date", "state": "State", "status": "Status", + "step_delete": "Delete step", + "step_delete_confirm": "Are you sure you want to delete this step?", + "step_details": "Step details", + "steps": "Steps", + "steps_count": "{count, plural, one {# step} other {# steps}}", "stop_casting": "Stop casting", "stop_motion_photo": "Stop Motion Photo", "stop_photo_sharing": "Stop sharing your photos?", @@ -2291,7 +2364,7 @@ "trash_page_title": "Trash ({count})", "trashed_items_will_be_permanently_deleted_after": "Trashed items will be permanently deleted after {days, plural, one {# day} other {# days}}.", "trigger": "Trigger", - "trigger_asset_uploaded": "Asset Uploaded", + "trigger_asset_uploaded": "Asset Upload", "trigger_asset_uploaded_description": "Triggered when a new asset is uploaded", "trigger_description": "An event that kicks off the workflow", "trigger_person_recognized": "Person Recognized", @@ -2331,7 +2404,6 @@ "unsupported_field_type": "Unsupported field type", "unsupported_file_type": "File {file} can't be uploaded because its file type {type} is not supported.", "untagged": "Untagged", - "untitled_workflow": "Untitled workflow", "up_next": "Up next", "update_location_action_prompt": "Update the location of {count} selected assets with:", "updated_at": "Updated", @@ -2360,6 +2432,7 @@ "use_browser_locale_description": "Format dates, times, and numbers based on your browser locale", "use_current_connection": "Use current connection", "use_custom_date_range": "Use custom date range instead", + "use_template": "Use template", "user": "User", "user_has_been_deleted": "This user has been deleted.", "user_id": "User ID", @@ -2389,6 +2462,7 @@ "video": "Video", "video_hover_setting": "Play video thumbnail on hover", "video_hover_setting_description": "Play video thumbnail when mouse is hovering over item. Even when disabled, playback can be started by hovering over the play icon.", + "video_quality": "Video quality", "videos": "Videos", "videos_count": "{count, plural, one {# Video} other {# Videos}}", "videos_only": "Videos only", @@ -2421,8 +2495,10 @@ "week": "Week", "welcome": "Welcome", "welcome_to_immich": "Welcome to Immich", + "when": "When", "width": "Width", "wifi_name": "Wi-Fi Name", + "workflow": "Workflow", "workflow_delete_prompt": "Are you sure you want to delete this workflow?", "workflow_deleted": "Workflow deleted", "workflow_description": "Workflow description", @@ -2432,6 +2508,7 @@ "workflow_name": "Workflow name", "workflow_navigation_prompt": "Are you sure you want to leave without saving your changes?", "workflow_summary": "Workflow summary", + "workflow_templates": "Workflow templates", "workflow_update_success": "Workflow updated successfully", "workflow_updated": "Workflow updated", "workflows": "Workflows", diff --git a/i18n/package.json b/i18n/package.json deleted file mode 100644 index 2b9548ed8b..0000000000 --- a/i18n/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "immich-i18n", - "version": "2.7.5", - "private": true, - "scripts": { - "format": "prettier --cache --check .", - "format:fix": "prettier --cache --write --list-different ." - }, - "devDependencies": { - "prettier": "^3.7.4", - "prettier-plugin-sort-json": "^4.1.1" - } -} diff --git a/machine-learning/Dockerfile b/machine-learning/Dockerfile index c6f9f01675..f6bbe9cebd 100644 --- a/machine-learning/Dockerfile +++ b/machine-learning/Dockerfile @@ -1,8 +1,8 @@ ARG DEVICE=cpu -FROM python:3.11-bookworm@sha256:970c99f886b839fc8829289040c1845dadaf2cae46b37acc7710333158ec29b4 AS builder-cpu +FROM python:3.11-bookworm@sha256:121d86b6d08752968a7dddbc708849e5f3a839bbff47f32212b46d2a1d842bab AS builder-cpu -FROM python:3.13-slim-trixie@sha256:d168b8d9eb761f4d3fe305ebd04aeb7e7f2de0297cec5fb2f8f6403244621664 AS builder-openvino +FROM python:3.13-slim-trixie@sha256:b04b5d7233d2ad9c379e22ea8927cd1378cd15c60d4ef876c065b25ea8fb3bf3 AS builder-openvino FROM builder-cpu AS builder-cuda @@ -39,12 +39,12 @@ RUN --mount=type=cache,target=/root/.cache/uv \ --mount=type=bind,source=pyproject.toml,target=pyproject.toml \ uv sync --frozen --extra ${DEVICE} --no-dev --no-editable --no-install-project --compile-bytecode --no-progress --active --link-mode copy -FROM python:3.11-slim-bookworm@sha256:9c6f90801e6b68e772b7c0ca74260cbf7af9f320acec894e26fccdaccfbe3b47 AS prod-cpu +FROM python:3.11-slim-bookworm@sha256:8dca233de9f3d9bb410665f00a4da6dd06f331083137e0e98ccf227236fcc438 AS prod-cpu ENV LD_PRELOAD=/usr/lib/libmimalloc.so.2 \ MACHINE_LEARNING_MODEL_ARENA=false -FROM python:3.13-slim-trixie@sha256:d168b8d9eb761f4d3fe305ebd04aeb7e7f2de0297cec5fb2f8f6403244621664 AS prod-openvino +FROM python:3.13-slim-trixie@sha256:b04b5d7233d2ad9c379e22ea8927cd1378cd15c60d4ef876c065b25ea8fb3bf3 AS prod-openvino RUN apt-get update && \ apt-get install --no-install-recommends -yqq ocl-icd-libopencl1 wget && \ diff --git a/machine-learning/immich_ml/__main__.py b/machine-learning/immich_ml/__main__.py index 8d575a58d5..f305c77657 100644 --- a/machine-learning/immich_ml/__main__.py +++ b/machine-learning/immich_ml/__main__.py @@ -49,6 +49,7 @@ try: str(settings.http_keepalive_timeout_s), "--graceful-timeout", "10", + "--no-control-socket", ], ) as cmd: cmd.wait() diff --git a/machine-learning/immich_ml/config.py b/machine-learning/immich_ml/config.py index 8b383f5419..d1e44fd70a 100644 --- a/machine-learning/immich_ml/config.py +++ b/machine-learning/immich_ml/config.py @@ -6,7 +6,7 @@ from pathlib import Path from socket import socket from gunicorn.arbiter import Arbiter -from pydantic import BaseModel +from pydantic import BaseModel, Field from pydantic_settings import BaseSettings, SettingsConfigDict from rich.console import Console from rich.logging import RichHandler @@ -32,29 +32,20 @@ class OcrSettings(BaseModel): class PreloadModelData(BaseModel): - clip_fallback: str | None = os.getenv("MACHINE_LEARNING_PRELOAD__CLIP", None) - facial_recognition_fallback: str | None = os.getenv("MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION", None) - if clip_fallback is not None: - os.environ["MACHINE_LEARNING_PRELOAD__CLIP__TEXTUAL"] = clip_fallback - os.environ["MACHINE_LEARNING_PRELOAD__CLIP__VISUAL"] = clip_fallback - del os.environ["MACHINE_LEARNING_PRELOAD__CLIP"] - if facial_recognition_fallback is not None: - os.environ["MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__RECOGNITION"] = facial_recognition_fallback - os.environ["MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__DETECTION"] = facial_recognition_fallback - del os.environ["MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION"] clip: ClipSettings = ClipSettings() facial_recognition: FacialRecognitionSettings = FacialRecognitionSettings() ocr: OcrSettings = OcrSettings() class MaxBatchSize(BaseModel): - ocr_fallback: str | None = os.getenv("MACHINE_LEARNING_MAX_BATCH_SIZE__TEXT_RECOGNITION", None) - if ocr_fallback is not None: - os.environ["MACHINE_LEARNING_MAX_BATCH_SIZE__OCR"] = ocr_fallback facial_recognition: int | None = None ocr: int | None = None +def default_worker_timeout() -> int: + return 900 if os.environ.get("DEVICE") == "rocm" else 300 + + class Settings(BaseSettings): model_config = SettingsConfigDict( env_prefix="MACHINE_LEARNING_", @@ -67,7 +58,7 @@ class Settings(BaseSettings): model_ttl: int = 300 model_ttl_poll_s: int = 10 workers: int = 1 - worker_timeout: int = 300 + worker_timeout: int = Field(default_factory=default_worker_timeout) http_keepalive_timeout_s: int = 2 test_full: bool = False request_threads: int = os.cpu_count() or 4 diff --git a/machine-learning/immich_ml/main.py b/machine-learning/immich_ml/main.py index 4fca7a2e2b..4ac5b020ae 100644 --- a/machine-learning/immich_ml/main.py +++ b/machine-learning/immich_ml/main.py @@ -12,7 +12,7 @@ from zipfile import BadZipFile import orjson from fastapi import Depends, FastAPI, File, Form, HTTPException -from fastapi.responses import ORJSONResponse, PlainTextResponse +from fastapi.responses import PlainTextResponse from onnxruntime.capi.onnxruntime_pybind11_state import InvalidProtobuf, NoSuchFile from PIL.Image import Image from pydantic import ValidationError @@ -32,6 +32,7 @@ from .schemas import ( ModelIdentity, ModelTask, ModelType, + ORJSONResponse, PipelineRequest, T, ) @@ -117,20 +118,6 @@ async def preload_models(preload: PreloadModelData) -> None: ModelTask.OCR, ) - if preload.clip_fallback is not None: - log.warning( - "Deprecated env variable: 'MACHINE_LEARNING_PRELOAD__CLIP'. " - "Use 'MACHINE_LEARNING_PRELOAD__CLIP__TEXTUAL' and " - "'MACHINE_LEARNING_PRELOAD__CLIP__VISUAL' instead." - ) - - if preload.facial_recognition_fallback is not None: - log.warning( - "Deprecated env variable: 'MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION'. " - "Use 'MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__DETECTION' and " - "'MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__RECOGNITION' instead." - ) - def update_state() -> Iterator[None]: global active_requests, last_called diff --git a/machine-learning/immich_ml/models/clip/textual.py b/machine-learning/immich_ml/models/clip/textual.py index 7e1908e120..989f46a2af 100644 --- a/machine-learning/immich_ml/models/clip/textual.py +++ b/machine-learning/immich_ml/models/clip/textual.py @@ -89,7 +89,9 @@ class OpenClipTextualEncoder(BaseCLIPTextualEncoder): tokenizer: Tokenizer = Tokenizer.from_file(self.tokenizer_file_path.as_posix()) - pad_id: int = tokenizer.token_to_id(pad_token) + pad_id = tokenizer.token_to_id(pad_token) + if pad_id is None: + raise ValueError(f"Pad token '{pad_token}' not found in tokenizer vocab") tokenizer.enable_padding(length=context_length, pad_token=pad_token, pad_id=pad_id) tokenizer.enable_truncation(max_length=context_length) diff --git a/machine-learning/immich_ml/models/facial_recognition/recognition.py b/machine-learning/immich_ml/models/facial_recognition/recognition.py index ed1897c9f9..c144bd3eaa 100644 --- a/machine-learning/immich_ml/models/facial_recognition/recognition.py +++ b/machine-learning/immich_ml/models/facial_recognition/recognition.py @@ -89,4 +89,10 @@ class FaceRecognizer(InferenceModel): @property def _batch_size_default(self) -> int | None: providers = ort.get_available_providers() - return None if self.model_format == ModelFormat.ONNX and "OpenVINOExecutionProvider" not in providers else 1 + if ( + self.model_format == ModelFormat.ONNX + and "MIGraphXExecutionProvider" not in providers + and "OpenVINOExecutionProvider" not in providers + ): + return None + return 1 diff --git a/machine-learning/immich_ml/models/ocr/recognition.py b/machine-learning/immich_ml/models/ocr/recognition.py index 6408e4818f..94f40c9285 100644 --- a/machine-learning/immich_ml/models/ocr/recognition.py +++ b/machine-learning/immich_ml/models/ocr/recognition.py @@ -64,6 +64,7 @@ class TextRecognizer(InferenceModel): rec_batch_num=max_batch_size if max_batch_size else 6, rec_img_shape=(3, 48, 320), lang_type=self.language, + model_root_dir=self.cache_dir, ) ) return session diff --git a/machine-learning/immich_ml/schemas.py b/machine-learning/immich_ml/schemas.py index 41706180de..d5304cf763 100644 --- a/machine-learning/immich_ml/schemas.py +++ b/machine-learning/immich_ml/schemas.py @@ -3,9 +3,16 @@ from typing import Any, Literal, Protocol, TypeGuard, TypeVar import numpy as np import numpy.typing as npt +import orjson +from fastapi.responses import JSONResponse from typing_extensions import TypedDict +class ORJSONResponse(JSONResponse): + def render(self, content: Any) -> bytes: + return orjson.dumps(content, option=orjson.OPT_SERIALIZE_NUMPY) + + class StrEnum(str, Enum): value: str diff --git a/machine-learning/immich_ml/sessions/ort.py b/machine-learning/immich_ml/sessions/ort.py index bebd235970..579cafc1cd 100644 --- a/machine-learning/immich_ml/sessions/ort.py +++ b/machine-learning/immich_ml/sessions/ort.py @@ -1,6 +1,7 @@ from __future__ import annotations from pathlib import Path +from threading import Lock from typing import Any import numpy as np @@ -12,6 +13,37 @@ from immich_ml.schemas import ModelPrecision, SessionNode from ..config import log, settings +MigraphxInputSignature = tuple[tuple[str, str, tuple[int, ...]], ...] + +_migraphx_registry_lock = Lock() +_migraphx_model_locks: dict[str, Lock] = {} +_migraphx_compiled_inputs: set[tuple[str, MigraphxInputSignature]] = set() + + +def _migraphx_get_model_lock(model_key: str) -> Lock: + with _migraphx_registry_lock: + lock = _migraphx_model_locks.get(model_key) + if lock is None: + lock = Lock() + _migraphx_model_locks[model_key] = lock + return lock + + +def _migraphx_has_compiled_input(key: tuple[str, MigraphxInputSignature]) -> bool: + with _migraphx_registry_lock: + return key in _migraphx_compiled_inputs + + +def _migraphx_mark_compiled_input(key: tuple[str, MigraphxInputSignature]) -> None: + with _migraphx_registry_lock: + _migraphx_compiled_inputs.add(key) + + +def _migraphx_input_signature( + input_feed: dict[str, NDArray[np.float32]] | dict[str, NDArray[np.int32]], +) -> MigraphxInputSignature: + return tuple((name, str(value.dtype), tuple(value.shape)) for name, value in sorted(input_feed.items())) + class OrtSession: session: ort.InferenceSession @@ -48,7 +80,21 @@ class OrtSession: input_feed: dict[str, NDArray[np.float32]] | dict[str, NDArray[np.int32]], run_options: Any = None, ) -> list[NDArray[np.float32]]: - outputs: list[NDArray[np.float32]] = self.session.run(output_names, input_feed, run_options) + if "MIGraphXExecutionProvider" in self.providers: + model_key = self.model_path.resolve().as_posix() + input_key = (model_key, _migraphx_input_signature(input_feed)) + if not _migraphx_has_compiled_input(input_key): + model_lock = _migraphx_get_model_lock(model_key) + with model_lock: + if not _migraphx_has_compiled_input(input_key): + outputs: list[NDArray[np.float32]] = self.session.run(output_names, input_feed, run_options) + _migraphx_mark_compiled_input(input_key) + return outputs + + outputs = self.session.run(output_names, input_feed, run_options) + return outputs + + outputs = self.session.run(output_names, input_feed, run_options) return outputs @property diff --git a/machine-learning/mise.lock b/machine-learning/mise.lock new file mode 100644 index 0000000000..b9788e603b --- /dev/null +++ b/machine-learning/mise.lock @@ -0,0 +1,72 @@ +# @generated - this file is auto-generated by `mise lock` https://mise.en.dev/dev-tools/mise-lock.html + +[[tools.python]] +version = "3.11.15" +backend = "core:python" + +[tools.python."platforms.linux-arm64"] +checksum = "sha256:243f794278eff6adba96ed3677ec6877175df84c25f140e17f09f9be82d0f12a" +url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260510/cpython-3.11.15+20260510-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz" +provenance = "github-attestations" + +[tools.python."platforms.linux-arm64-musl"] +checksum = "sha256:52b4c52094ff8b383a45c694acf4c5c0e883152be6d5229a35a8186ce907c6eb" +url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260510/cpython-3.11.15+20260510-aarch64-unknown-linux-musl-install_only_stripped.tar.gz" +provenance = "github-attestations" + +[tools.python."platforms.linux-x64"] +checksum = "sha256:171dffd8c0f66e8a0725364a7428015b22fc18dd298b24f541392e17dd0e561f" +url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260510/cpython-3.11.15+20260510-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz" +provenance = "github-attestations" + +[tools.python."platforms.linux-x64-musl"] +checksum = "sha256:2ac90fef8917ebd14826a6d667593a06cf0ae5f745ba9b1147dc086dd35f5284" +url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260510/cpython-3.11.15+20260510-x86_64-unknown-linux-musl-install_only_stripped.tar.gz" +provenance = "github-attestations" + +[tools.python."platforms.macos-arm64"] +checksum = "sha256:fdfc363b538662eb7441a14e06f72c4a992c56af7f401f5730ea5081f8f8ad6e" +url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260510/cpython-3.11.15+20260510-aarch64-apple-darwin-install_only_stripped.tar.gz" +provenance = "github-attestations" + +[tools.python."platforms.macos-x64"] +checksum = "sha256:5f1eb247cbca2c0ad5ccbf6d299a4f54b31b5c63b492d74c3531dc4344a42f88" +url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260510/cpython-3.11.15+20260510-x86_64-apple-darwin-install_only_stripped.tar.gz" +provenance = "github-attestations" + +[tools.python."platforms.windows-x64"] +checksum = "sha256:756d7f148498b8822f6aedf44a020613576f09983161f346ad36dcef6238cdc3" +url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260510/cpython-3.11.15+20260510-x86_64-pc-windows-msvc-install_only_stripped.tar.gz" +provenance = "github-attestations" + +[[tools.uv]] +version = "0.8.15" +backend = "aqua:astral-sh/uv" + +[tools.uv."platforms.linux-arm64"] +checksum = "sha256:23ea21a05c62c4c307ce691f29bff2f15c94c4f07f2b83d9b356f0664bc8b3a2" +url = "https://github.com/astral-sh/uv/releases/download/0.8.15/uv-aarch64-unknown-linux-musl.tar.gz" + +[tools.uv."platforms.linux-arm64-musl"] +checksum = "sha256:23ea21a05c62c4c307ce691f29bff2f15c94c4f07f2b83d9b356f0664bc8b3a2" +url = "https://github.com/astral-sh/uv/releases/download/0.8.15/uv-aarch64-unknown-linux-musl.tar.gz" + +[tools.uv."platforms.linux-x64"] +checksum = "sha256:d0fec58f3124e05e0a1af0f6541abfce4333253cdaf23c7b6bb2e6128bf138ea" +url = "https://github.com/astral-sh/uv/releases/download/0.8.15/uv-x86_64-unknown-linux-musl.tar.gz" + +[tools.uv."platforms.linux-x64-musl"] +checksum = "sha256:d0fec58f3124e05e0a1af0f6541abfce4333253cdaf23c7b6bb2e6128bf138ea" +url = "https://github.com/astral-sh/uv/releases/download/0.8.15/uv-x86_64-unknown-linux-musl.tar.gz" + +[tools.uv."platforms.macos-arm64"] +checksum = "sha256:103367962c5cb00bf7370d84cbaa3fec5a9807be9cc833ea9d8eea400c119fa2" +url = "https://github.com/astral-sh/uv/releases/download/0.8.15/uv-aarch64-apple-darwin.tar.gz" + +[tools.uv."platforms.macos-x64"] +checksum = "sha256:2bbef70982e97dfc36454de173f35ec1a5e83ae11e3885df6a50db3fd76171cb" +url = "https://github.com/astral-sh/uv/releases/download/0.8.15/uv-x86_64-apple-darwin.tar.gz" + +[tools.uv."platforms.windows-x64"] +checksum = "sha256:459d95892a5cc5c21779532f4f41b9238594b79e312a5142da2148ecfa10e705" +url = "https://github.com/astral-sh/uv/releases/download/0.8.15/uv-x86_64-pc-windows-msvc.zip" diff --git a/machine-learning/mise.toml b/machine-learning/mise.toml new file mode 100644 index 0000000000..e5e30c4fc2 --- /dev/null +++ b/machine-learning/mise.toml @@ -0,0 +1,36 @@ +[tools] +python = "3.11" +uv = "0.8.15" + +[tasks.install] +run = "uv sync --locked" + +[tasks.lint] +run = "uv run ruff check immich_ml" + +[tasks.test] +run = "uv run pytest --cov=immich_ml --cov-report term-missing" + +[tasks.format] +run = "uv run ruff format immich_ml" + +[tasks.check] +run = "uv run mypy --strict immich_ml/" + +[tasks.ci-unit] +run = [ + { task = ":install --extra cpu" }, + { task = ":format" }, + { task = ":lint --output-format=github" }, + { task = ":check" }, + { task = ":test" }, +] + +[tasks.checklist] +run = [ + { task = ":install" }, + { task = ":format" }, + { task = ":lint" }, + { task = ":check" }, + { task = ":test" }, +] diff --git a/machine-learning/pyproject.toml b/machine-learning/pyproject.toml index d61df51e38..4a74c6405e 100644 --- a/machine-learning/pyproject.toml +++ b/machine-learning/pyproject.toml @@ -10,8 +10,8 @@ dependencies = [ "fastapi>=0.95.2,<1.0", "gunicorn>=21.1.0", "huggingface-hub>=1.0,<2.0", - "insightface>=0.7.3,<1.0", - "numpy<2.4.0", + "insightface>=0.7.3,<2.0", + "numpy>=2.4.0,<3.0", "opencv-python-headless>=4.7.0.72,<5.0", "orjson>=3.9.5", "pillow>=12.2,<13", diff --git a/machine-learning/test_main.py b/machine-learning/test_main.py index cce334e40e..be574c6397 100644 --- a/machine-learning/test_main.py +++ b/machine-learning/test_main.py @@ -35,7 +35,37 @@ from immich_ml.sessions.ort import OrtSession from immich_ml.sessions.rknn import RknnSession, run_inference +class FakeLock: + def __init__(self) -> None: + self.enter = mock.Mock() + self.exit = mock.Mock() + + def __enter__(self) -> None: + self.enter() + + def __exit__(self, *args: object) -> None: + self.exit(*args) + + class TestBase: + def test_sets_default_worker_timeout(self, monkeypatch: MonkeyPatch) -> None: + monkeypatch.delenv("DEVICE", raising=False) + monkeypatch.delenv("MACHINE_LEARNING_WORKER_TIMEOUT", raising=False) + + assert Settings().worker_timeout == 300 + + def test_sets_rocm_default_worker_timeout(self, monkeypatch: MonkeyPatch) -> None: + monkeypatch.setenv("DEVICE", "rocm") + monkeypatch.delenv("MACHINE_LEARNING_WORKER_TIMEOUT", raising=False) + + assert Settings().worker_timeout == 900 + + def test_worker_timeout_env_override(self, monkeypatch: MonkeyPatch) -> None: + monkeypatch.setenv("DEVICE", "rocm") + monkeypatch.setenv("MACHINE_LEARNING_WORKER_TIMEOUT", "1200") + + assert Settings().worker_timeout == 1200 + def test_sets_default_cache_dir(self) -> None: encoder = OpenClipTextualEncoder("ViT-B-32__openai") @@ -413,6 +443,52 @@ class TestOrtSession: assert sess_options is session.sess_options + def test_serializes_rocm_first_run_for_new_input_signature(self, mocker: MockerFixture) -> None: + lock = FakeLock() + get_model_lock = mocker.patch("immich_ml.sessions.ort._migraphx_get_model_lock", return_value=lock) + mocker.patch("immich_ml.sessions.ort._migraphx_compiled_inputs", set()) + mocker.patch("immich_ml.sessions.ort.Path.mkdir") + session = OrtSession("/cache/ViT-B-32__openai/model.onnx", providers=["MIGraphXExecutionProvider"]) + input_feed = {"input": np.random.rand(1, 3, 224, 224).astype(np.float32)} + + session.run(None, input_feed) + session.run(None, input_feed) + + lock.enter.assert_called_once() + lock.exit.assert_called_once() + get_model_lock.assert_called_once() + session.session.run.assert_has_calls([mock.call(None, input_feed, None), mock.call(None, input_feed, None)]) + + def test_serializes_rocm_run_for_each_new_input_signature(self, mocker: MockerFixture) -> None: + lock = FakeLock() + mocker.patch("immich_ml.sessions.ort._migraphx_get_model_lock", return_value=lock) + mocker.patch("immich_ml.sessions.ort._migraphx_compiled_inputs", set()) + mocker.patch("immich_ml.sessions.ort.Path.mkdir") + session = OrtSession("/cache/ViT-B-32__openai/model.onnx", providers=["MIGraphXExecutionProvider"]) + input_feed = {"input": np.random.rand(1, 3, 224, 224).astype(np.float32)} + new_shape_input_feed = {"input": np.random.rand(2, 3, 224, 224).astype(np.float32)} + + session.run(None, input_feed) + session.run(None, new_shape_input_feed) + + assert lock.enter.call_count == 2 + assert lock.exit.call_count == 2 + session.session.run.assert_has_calls( + [mock.call(None, input_feed, None), mock.call(None, new_shape_input_feed, None)] + ) + + def test_does_not_serialize_non_rocm_run(self, mocker: MockerFixture) -> None: + lock = FakeLock() + get_model_lock = mocker.patch("immich_ml.sessions.ort._migraphx_get_model_lock", return_value=lock) + session = OrtSession("/cache/ViT-B-32__openai/model.onnx", providers=["CPUExecutionProvider"]) + input_feed = {"input": np.random.rand(1, 3, 224, 224).astype(np.float32)} + + session.run(None, input_feed) + + get_model_lock.assert_not_called() + lock.enter.assert_not_called() + session.session.run.assert_called_once_with(None, input_feed, None) + class TestAnnSession: def test_creates_ann_session(self, ann_session: mock.Mock, info: mock.Mock) -> None: @@ -740,6 +816,10 @@ class TestFaceRecognition: def test_recognition(self, cv_image: cv2.Mat, mocker: MockerFixture) -> None: mocker.patch.object(FaceRecognizer, "load") + mocker.patch( + "immich_ml.models.facial_recognition.recognition.ort.get_available_providers", + return_value=["CPUExecutionProvider"], + ) face_recognizer = FaceRecognizer("buffalo_s", min_score=0.0, cache_dir="test_cache") num_faces = 2 @@ -784,6 +864,10 @@ class TestFaceRecognition: ) mocker.patch("immich_ml.models.base.InferenceModel.download") mocker.patch("immich_ml.models.facial_recognition.recognition.ArcFaceONNX") + mocker.patch( + "immich_ml.models.facial_recognition.recognition.ort.get_available_providers", + return_value=["CPUExecutionProvider"], + ) ort_session.return_value.get_inputs.return_value = [SimpleNamespace(name="input.1", shape=(1, 3, 224, 224))] ort_session.return_value.get_outputs.return_value = [SimpleNamespace(name="output.1", shape=(1, 800))] path.return_value.__truediv__.return_value.__truediv__.return_value.suffix = ".onnx" @@ -818,6 +902,10 @@ class TestFaceRecognition: ) mocker.patch("immich_ml.models.base.InferenceModel.download") mocker.patch("immich_ml.models.facial_recognition.recognition.ArcFaceONNX") + mocker.patch( + "immich_ml.models.facial_recognition.recognition.ort.get_available_providers", + return_value=["CPUExecutionProvider"], + ) path.return_value.__truediv__.return_value.__truediv__.return_value.suffix = ".onnx" inputs = [SimpleNamespace(name="input.1", shape=("batch", 3, 224, 224))] @@ -883,6 +971,34 @@ class TestFaceRecognition: onnx.load.assert_not_called() onnx.save.assert_not_called() + def test_recognition_does_not_add_batch_axis_for_migraphx( + self, ort_session: mock.Mock, path: mock.Mock, mocker: MockerFixture + ) -> None: + onnx = mocker.patch("immich_ml.models.facial_recognition.recognition.onnx", autospec=True) + update_dims = mocker.patch( + "immich_ml.models.facial_recognition.recognition.update_inputs_outputs_dims", autospec=True + ) + mocker.patch("immich_ml.models.base.InferenceModel.download") + mocker.patch("immich_ml.models.facial_recognition.recognition.ArcFaceONNX") + mocker.patch( + "immich_ml.models.facial_recognition.recognition.ort.get_available_providers", + return_value=["MIGraphXExecutionProvider", "CPUExecutionProvider"], + ) + path.return_value.__truediv__.return_value.__truediv__.return_value.suffix = ".onnx" + + inputs = [SimpleNamespace(name="input.1", shape=(1, 3, 224, 224))] + outputs = [SimpleNamespace(name="output.1", shape=(1, 800))] + ort_session.return_value.get_inputs.return_value = inputs + ort_session.return_value.get_outputs.return_value = outputs + + face_recognizer = FaceRecognizer("buffalo_s", cache_dir=path) + face_recognizer.load() + + assert face_recognizer.batch_size == 1 + update_dims.assert_not_called() + onnx.load.assert_not_called() + onnx.save.assert_not_called() + def test_set_custom_max_batch_size(self, mocker: MockerFixture) -> None: mocker.patch.object(settings, "max_batch_size", MaxBatchSize(facial_recognition=2)) @@ -892,6 +1008,10 @@ class TestFaceRecognition: def test_ignore_other_custom_max_batch_size(self, mocker: MockerFixture) -> None: mocker.patch.object(settings, "max_batch_size", MaxBatchSize(ocr=2)) + mocker.patch( + "immich_ml.models.facial_recognition.recognition.ort.get_available_providers", + return_value=["CPUExecutionProvider"], + ) recognizer = FaceRecognizer("buffalo_l", cache_dir="test_cache") @@ -924,7 +1044,12 @@ class TestOcr: text_recognizer.load() rapid_recognizer.assert_called_once_with( - OcrOptions(session=ort_session.return_value, rec_batch_num=6, rec_img_shape=(3, 48, 320)) + OcrOptions( + session=ort_session.return_value, + rec_batch_num=6, + rec_img_shape=(3, 48, 320), + model_root_dir=text_recognizer.cache_dir, + ) ) def test_set_custom_max_batch_size(self, ort_session: mock.Mock, path: mock.Mock, mocker: MockerFixture) -> None: @@ -937,7 +1062,12 @@ class TestOcr: text_recognizer.load() rapid_recognizer.assert_called_once_with( - OcrOptions(session=ort_session.return_value, rec_batch_num=4, rec_img_shape=(3, 48, 320)) + OcrOptions( + session=ort_session.return_value, + rec_batch_num=4, + rec_img_shape=(3, 48, 320), + model_root_dir=text_recognizer.cache_dir, + ) ) def test_ignore_other_custom_max_batch_size( @@ -952,7 +1082,12 @@ class TestOcr: text_recognizer.load() rapid_recognizer.assert_called_once_with( - OcrOptions(session=ort_session.return_value, rec_batch_num=6, rec_img_shape=(3, 48, 320)) + OcrOptions( + session=ort_session.return_value, + rec_batch_num=6, + rec_img_shape=(3, 48, 320), + model_root_dir=text_recognizer.cache_dir, + ) ) diff --git a/machine-learning/uv.lock b/machine-learning/uv.lock index 894acf77f5..933d19d23d 100644 --- a/machine-learning/uv.lock +++ b/machine-learning/uv.lock @@ -2,11 +2,14 @@ version = 1 revision = 3 requires-python = ">=3.11, <4.0" resolution-markers = [ - "python_full_version >= '3.13' and sys_platform == 'darwin'", + "python_full_version >= '3.15' and sys_platform == 'darwin'", + "python_full_version >= '3.13' and python_full_version < '3.15' and sys_platform == 'darwin'", "python_full_version == '3.12.*' and sys_platform == 'darwin'", - "python_full_version >= '3.13' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "python_full_version >= '3.15' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "python_full_version >= '3.13' and python_full_version < '3.15' and platform_machine == 'aarch64' and sys_platform == 'linux'", "python_full_version == '3.12.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "(python_full_version >= '3.13' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.13' and sys_platform != 'darwin' and sys_platform != 'linux')", + "(python_full_version >= '3.15' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.15' and sys_platform != 'darwin' and sys_platform != 'linux')", + "(python_full_version >= '3.13' and python_full_version < '3.15' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.13' and python_full_version < '3.15' and sys_platform != 'darwin' and sys_platform != 'linux')", "(python_full_version == '3.12.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and sys_platform != 'linux')", "python_full_version < '3.12' and sys_platform == 'darwin'", "python_full_version < '3.12' and platform_machine == 'aarch64' and sys_platform == 'linux'", @@ -76,6 +79,46 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/bf/cd/d6d9bb1dadf73e7af02d18225cbd2c93f8552e13130484f1c8dcfece292b/anyio-4.2.0-py3-none-any.whl", hash = "sha256:745843b39e829e108e518c489b31dc757de7d2131d53fac32bd8df268227bfee", size = 85481, upload-time = "2023-12-16T17:06:55.989Z" }, ] +[[package]] +name = "ast-serialize" +version = "0.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/81/9d/09e27731bd5864a9ce04e3244074e674bb8936bf62b45e0357248717adac/ast_serialize-0.5.0.tar.gz", hash = "sha256:5880091bfe6f4f986f22866375c2e884843e7a0b6343ae41aeea659613d879b6", size = 61157, upload-time = "2026-05-17T17:48:29.429Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/9a/13dde51ba9e15f8b97957ab7cb0120d0e381524d651c6bd630b9c359227f/ast_serialize-0.5.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8f5c14f169eb0972c0c21bada5358b23d6047c76583b005234f865b11f1fa00a", size = 1183520, upload-time = "2026-05-17T17:47:30.831Z" }, + { url = "https://files.pythonhosted.org/packages/37/de/5a7f0a9fe68944f536632a5af84676739c7d2582be42deb082634bf3a754/ast_serialize-0.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7d1a2de9de5be04652f0ed60738356ef94f66db37924a9499fffe98dc491aa0b", size = 1175779, upload-time = "2026-05-17T17:47:32.551Z" }, + { url = "https://files.pythonhosted.org/packages/9c/81/0bb853e76e4f6e9a1855d569003c59e19ffac45f7079d91505d1bb212f92/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be5173fb66f9b49026d9d5a2ff0fc7c7009077107c0eb285b2d60fdf1fe10bd1", size = 1233750, upload-time = "2026-05-17T17:47:34.731Z" }, + { url = "https://files.pythonhosted.org/packages/e5/d3/4cf705beeccc08754d0bbda99aefff26110e209b9a07ac8a6b60eec48531/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f8015cd071ac1339924ee2b8098c93e00e155f30a16f40ec9816fcf84f4753f6", size = 1235942, upload-time = "2026-05-17T17:47:36.287Z" }, + { url = "https://files.pythonhosted.org/packages/26/c8/ee097e437ea27dd2b8b227865c875492b585650a5802a22d82b304c8201b/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5499e8797edff2a9186aa313ed382c6b422e798e9332d9953badcee6e69a88f2", size = 1442517, upload-time = "2026-05-17T17:47:38.17Z" }, + { url = "https://files.pythonhosted.org/packages/ff/bd/68063442838f1ba68ec72b5436430bc75b3bb17a1a3c3063f09b0c05ae2b/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6848f2a093fb5548751a9a09bff8fcd229e2bbeb0e3331f391b6ae6d26cd9903", size = 1254081, upload-time = "2026-05-17T17:47:39.826Z" }, + { url = "https://files.pythonhosted.org/packages/50/e2/1e520793bc6a4e4524a6ab022391e827825eaa0c3811828bfdc6852eca26/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:832d4c998e0b091fd60a6d6bceee535483c4d490de9ba85003af835225719261", size = 1259910, upload-time = "2026-05-17T17:47:41.369Z" }, + { url = "https://files.pythonhosted.org/packages/4e/e1/49b60f467979979cfe6913b43948ff25bca971ad0591d181812f163a988e/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:16db7c62ec0b8efe1d7afd283a388d8f74f2605d56032e5a37747d2de8dba027", size = 1250678, upload-time = "2026-05-17T17:47:43.702Z" }, + { url = "https://files.pythonhosted.org/packages/74/ba/66ab9555de6275677566f6574e5ef6c29cb185ea866f643bc06f8280a8ee/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:baf5eb061eb5bccade4128ad42da33787d72f6013809cd1b590376ece8b3c937", size = 1301603, upload-time = "2026-05-17T17:47:46.256Z" }, + { url = "https://files.pythonhosted.org/packages/66/42/6aca9b9abc710014b2be9059689e5dd1679339e78f567ffb4d255a9e2050/ast_serialize-0.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:104e4a35bd7c124173c41760ef9aaea17ddb3f86c65cb643671d59afbe3ee94c", size = 1410332, upload-time = "2026-05-17T17:47:47.899Z" }, + { url = "https://files.pythonhosted.org/packages/47/68/2f76594432a22581ecf878b5e75a9b8601c24b2241cf0bbeb1e21fcf370c/ast_serialize-0.5.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:36be371028fc1675acb38a331bde160dbab7ff907fdf00b67eb6911aa106951b", size = 1509979, upload-time = "2026-05-17T17:47:50.942Z" }, + { url = "https://files.pythonhosted.org/packages/40/ac/a93c9b58292653f6c595752f677a08e608f903b710594909e9231a389b3b/ast_serialize-0.5.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:061ee58bdb52341c8201a6df41182a977736bae3b7ded87ca7176ca25a8a47ab", size = 1505002, upload-time = "2026-05-17T17:47:54.093Z" }, + { url = "https://files.pythonhosted.org/packages/14/2e/b278f68c497ee2f1d1576cbbef8db5281cd4a5f2db040537592ac9c8862e/ast_serialize-0.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b15219e9cdc9f53f6f4cb51c009203507228226148c05c5e8fe451c28b435eb3", size = 1456231, upload-time = "2026-05-17T17:47:56.311Z" }, + { url = "https://files.pythonhosted.org/packages/0b/43/419be1c566a4c504cd8fd60ce2f84e790f295495c0f327cfaeadf3d51012/ast_serialize-0.5.0-cp314-cp314t-win32.whl", hash = "sha256:842d1c004bb466c7df036f95fabef789570541922b10976b12f5592a69cf0b38", size = 1058668, upload-time = "2026-05-17T17:47:58.305Z" }, + { url = "https://files.pythonhosted.org/packages/03/6f/c9d4d549295ed05111aeb8853232d1afd9d0a179fddb01eeffbb3a4a6842/ast_serialize-0.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b0c06d760909b095cc466356dfccd05a1c7233a6ca191c020dca2c6a6f16c24c", size = 1101075, upload-time = "2026-05-17T17:48:00.35Z" }, + { url = "https://files.pythonhosted.org/packages/d0/8e/d00c5ab30c58222e07d62956fca86c59d91b9ad32997e633c38b526623a3/ast_serialize-0.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:787baedb0262cc49e8ce37cc15c00ae818e46a165a3b36f5e21ed174998104cb", size = 1075347, upload-time = "2026-05-17T17:48:01.753Z" }, + { url = "https://files.pythonhosted.org/packages/e0/9e/dc2530acb3a60dc6e46d65abf27d1d9f86721694757906a148d90a6860de/ast_serialize-0.5.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:0668aa9459cfa8c9c49ddd2163ebcf43088ba045ef7492af6fe22e0098303101", size = 1191380, upload-time = "2026-05-17T17:48:03.738Z" }, + { url = "https://files.pythonhosted.org/packages/26/0a/bd3d18a582f273d6c843d16bb9e22e9e16365ff7991e92f18f798e9f1224/ast_serialize-0.5.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:bf683d6363edf2b39eed6b6d4fe22d34b6203867a67e27134d9e2a2680c4bc4a", size = 1183879, upload-time = "2026-05-17T17:48:05.463Z" }, + { url = "https://files.pythonhosted.org/packages/40/ae/1f919100f8620887af58fcc381c61a1f218cdf89c6e155f87b213e61010a/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cc22cf0c9be65e71cf88fda130af60d61eb4a79370ad4cfe7900d48a4aa2211", size = 1244529, upload-time = "2026-05-17T17:48:07.008Z" }, + { url = "https://files.pythonhosted.org/packages/c6/ca/6376559dcce707cdbc1d0d9a13c8d3baaaa501e949ce0ebdc4230cd881aa/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f66173891548c9f2726bf27957b41cabce12fa679dc6da505ddbde4d4b3b31cf", size = 1240560, upload-time = "2026-05-17T17:48:08.46Z" }, + { url = "https://files.pythonhosted.org/packages/35/b2/a620e206b5aeb7efbf2710336df57d457cffbb3991076bbcc1147ef9abd4/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e42d729ef2be96a14efbad355093284739e3670ece3e534f82cc8832790911d9", size = 1451172, upload-time = "2026-05-17T17:48:09.922Z" }, + { url = "https://files.pythonhosted.org/packages/fa/e0/4ad5c04c24a40481b2935ce9a0ccdb6023dc8b667167d06ae530cc3512f2/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b725026bafa801dbd7310eb13a75f0a2e370e7e51b2cb225f9d21fcfadf919ee", size = 1265072, upload-time = "2026-05-17T17:48:11.469Z" }, + { url = "https://files.pythonhosted.org/packages/b2/71/4d1d479aa56d0101c40e17720c3d6ac2af7269ea0487a80b18e7bfd1a5b7/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b54f60c1d78767a53b67eaa663f0dfac3afe606aa07f1301572f588b73d64809", size = 1270488, upload-time = "2026-05-17T17:48:13.575Z" }, + { url = "https://files.pythonhosted.org/packages/6d/4f/0de1bbe06f6edef9fde4ed12ca8e7b3ec7e6e2bd4e672c5af487f7957665/ast_serialize-0.5.0-cp39-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:27d51654fc240a1e87e742d353d98eb45b75f62f129086b3596ab53df2ac2a43", size = 1260702, upload-time = "2026-05-17T17:48:15.141Z" }, + { url = "https://files.pythonhosted.org/packages/75/61/e00872439cfdddcc3c1b6cdaa6e5d904ba8e26a18807c67c4e14409d0ca8/ast_serialize-0.5.0-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c36237c46dd1674542f2109740ea5ea485a169bf1431939ada0434e17934", size = 1311182, upload-time = "2026-05-17T17:48:16.779Z" }, + { url = "https://files.pythonhosted.org/packages/76/8e/699a5b955f7926956c95e9e1d74132acad73c2fe7a426f94da89123c20aa/ast_serialize-0.5.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1943db345233cc7194a470f13afa9c59772c0b123dea0c9414c4d4ca54369759", size = 1421410, upload-time = "2026-05-17T17:48:18.527Z" }, + { url = "https://files.pythonhosted.org/packages/a9/ae/d5b7626874478997adc7a29ab28accf21e596fb590c944290401dfd0b29e/ast_serialize-0.5.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:df1c00022cbbcb064bfaa505aa9c9295362443ce5dacb459d1331d3da353f887", size = 1516587, upload-time = "2026-05-17T17:48:20.133Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ce/b59e02a82d9c4244d64cde502e0b00e83e38816abe19155ceb5437402c7f/ast_serialize-0.5.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:cae65289fc456fde04af979a2be09302ef5d8ab92ef23e596d6746dc267ada27", size = 1515171, upload-time = "2026-05-17T17:48:21.921Z" }, + { url = "https://files.pythonhosted.org/packages/8b/38/d8d90042747d05aa08d4efcf1c99035a5f670a6bf4c214d31644392afbca/ast_serialize-0.5.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:239a4c354e8d676e9d94631d1d4a64edc6b266f86ff3a5a80aedd344f342c01d", size = 1464668, upload-time = "2026-05-17T17:48:23.544Z" }, + { url = "https://files.pythonhosted.org/packages/dd/51/5b840c4df7334104cecffa28f23904fe81ca89ca223d2450e288de39fd3c/ast_serialize-0.5.0-cp39-abi3-win32.whl", hash = "sha256:143a4ef63285a075871908fda3672dc21864b83a8ec3ee12304aa3e4c5387b9a", size = 1068311, upload-time = "2026-05-17T17:48:25.027Z" }, + { url = "https://files.pythonhosted.org/packages/41/11/ca5672c7d491825bc4cd6702dea106a6b60d928707712ec257c7833ae476/ast_serialize-0.5.0-cp39-abi3-win_amd64.whl", hash = "sha256:cf25572c526add400f26a4750dc6ce0c3bb93fc1f75e7ae0cad4ce4f2cd5c590", size = 1108931, upload-time = "2026-05-17T17:48:26.591Z" }, + { url = "https://files.pythonhosted.org/packages/45/19/cc8bd127d28a43da249aa955cfd164cf8fd534e79e42cea96c4854d72fd0/ast_serialize-0.5.0-cp39-abi3-win_arm64.whl", hash = "sha256:92a31c9c20d25a076edaeec76b128a3535d74a24f340b9a8a7e96c9b86dc9642", size = 1081181, upload-time = "2026-05-17T17:48:28.122Z" }, +] + [[package]] name = "bidict" version = "0.23.1" @@ -243,14 +286,14 @@ wheels = [ [[package]] name = "click" -version = "8.1.7" +version = "8.4.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121, upload-time = "2023-08-17T17:29:11.868Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9b/98/518d8e5081007684232226f475082b30087d0f585e8457db087298259f49/click-8.4.1.tar.gz", hash = "sha256:918b5633eddf6b41c32d4f454bf0de810065c74e3f7dbf8ee5452f8be88d3e96", size = 353007, upload-time = "2026-05-22T04:08:37.769Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", size = 97941, upload-time = "2023-08-17T17:29:10.08Z" }, + { url = "https://files.pythonhosted.org/packages/c7/0d/67e5b4109ea4a837e80daa87c2c696711955e40449a97e8926672534def2/click-8.4.1-py3-none-any.whl", hash = "sha256:482be17c6991b8c19c5429a1e995d9b0efdbb63172824c41f99965dc0ade8ec2", size = 116639, upload-time = "2026-05-22T04:08:35.26Z" }, ] [[package]] @@ -511,7 +554,7 @@ sdist = { url = "https://files.pythonhosted.org/packages/0a/d2/deb3296d08097fedd [[package]] name = "fastapi" -version = "0.136.0" +version = "0.136.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-doc" }, @@ -520,9 +563,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4e/d9/e66315807e41e69e7f6a1b42a162dada2f249c5f06ad3f1a95f84ab336ef/fastapi-0.136.0.tar.gz", hash = "sha256:cf08e067cc66e106e102d9ba659463abfac245200752f8a5b7b1e813de4ff73e", size = 396607, upload-time = "2026-04-16T11:47:13.623Z" } +sdist = { url = "https://files.pythonhosted.org/packages/81/2d/ff8d91d7b564d464629a0fd50a4489c97fcb836ac230bf3a7269232a9b1f/fastapi-0.136.3.tar.gz", hash = "sha256:e487fae93ad408e6f47641ee4dfe389864fd7bec92e547ea8498fc13f43e83ab", size = 396410, upload-time = "2026-05-23T18:53:15.192Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/26/a3/0bd5f0cdb0bbc92650e8dc457e9250358411ee5d1b65e42b6632387daf81/fastapi-0.136.0-py3-none-any.whl", hash = "sha256:8793d44ec7378e2be07f8a013cf7f7aa47d6327d0dfe9804862688ec4541a6b4", size = 117556, upload-time = "2026-04-16T11:47:11.922Z" }, + { url = "https://files.pythonhosted.org/packages/e0/82/45359b62a067409bd929ae8a56b8ed13e5a8c8a61194b3c236920999ab83/fastapi-0.136.3-py3-none-any.whl", hash = "sha256:3d2a69bdf04b7e9f3afa292c3bc7a98816bbfafa10bc9b45f3f3700d2f761620", size = 117481, upload-time = "2026-05-23T18:53:16.924Z" }, ] [[package]] @@ -785,17 +828,34 @@ wheels = [ [[package]] name = "hf-xet" -version = "1.1.7" +version = "1.5.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b2/0a/a0f56735940fde6dd627602fec9ab3bad23f66a272397560abd65aba416e/hf_xet-1.1.7.tar.gz", hash = "sha256:20cec8db4561338824a3b5f8c19774055b04a8df7fff0cb1ff2cb1a0c1607b80", size = 477719, upload-time = "2025-08-06T00:30:55.741Z" } +sdist = { url = "https://files.pythonhosted.org/packages/74/d8/5c06fc76461418326a7decf8367480c35be11a41fd938633929c60a9ec6b/hf_xet-1.5.0.tar.gz", hash = "sha256:e0fb0a34d9f406eed88233e829a67ec016bec5af19e480eac65a233ea289a948", size = 837196, upload-time = "2026-05-06T06:18:15.583Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b1/7c/8d7803995caf14e7d19a392a486a040f923e2cfeff824e9b800b92072f76/hf_xet-1.1.7-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:60dae4b44d520819e54e216a2505685248ec0adbdb2dd4848b17aa85a0375cde", size = 2761743, upload-time = "2025-08-06T00:30:50.634Z" }, - { url = "https://files.pythonhosted.org/packages/51/a3/fa5897099454aa287022a34a30e68dbff0e617760f774f8bd1db17f06bd4/hf_xet-1.1.7-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:b109f4c11e01c057fc82004c9e51e6cdfe2cb230637644ade40c599739067b2e", size = 2624331, upload-time = "2025-08-06T00:30:49.212Z" }, - { url = "https://files.pythonhosted.org/packages/86/50/2446a132267e60b8a48b2e5835d6e24fd988000d0f5b9b15ebd6d64ef769/hf_xet-1.1.7-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6efaaf1a5a9fc3a501d3e71e88a6bfebc69ee3a716d0e713a931c8b8d920038f", size = 3183844, upload-time = "2025-08-06T00:30:47.582Z" }, - { url = "https://files.pythonhosted.org/packages/20/8f/ccc670616bb9beee867c6bb7139f7eab2b1370fe426503c25f5cbb27b148/hf_xet-1.1.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:751571540f9c1fbad9afcf222a5fb96daf2384bf821317b8bfb0c59d86078513", size = 3074209, upload-time = "2025-08-06T00:30:45.509Z" }, - { url = "https://files.pythonhosted.org/packages/21/0a/4c30e1eb77205565b854f5e4a82cf1f056214e4dc87f2918ebf83d47ae14/hf_xet-1.1.7-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:18b61bbae92d56ae731b92087c44efcac216071182c603fc535f8e29ec4b09b8", size = 3239602, upload-time = "2025-08-06T00:30:52.41Z" }, - { url = "https://files.pythonhosted.org/packages/f5/1e/fc7e9baf14152662ef0b35fa52a6e889f770a7ed14ac239de3c829ecb47e/hf_xet-1.1.7-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:713f2bff61b252f8523739969f247aa354ad8e6d869b8281e174e2ea1bb8d604", size = 3348184, upload-time = "2025-08-06T00:30:54.105Z" }, - { url = "https://files.pythonhosted.org/packages/a3/73/e354eae84ceff117ec3560141224724794828927fcc013c5b449bf0b8745/hf_xet-1.1.7-cp37-abi3-win_amd64.whl", hash = "sha256:2e356da7d284479ae0f1dea3cf5a2f74fdf925d6dca84ac4341930d892c7cb34", size = 2820008, upload-time = "2025-08-06T00:30:57.056Z" }, + { url = "https://files.pythonhosted.org/packages/68/9b/6912c99070915a4f28119e3c5b52a9abd1eec0ad5cb293b8c967a0c6f5a2/hf_xet-1.5.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:7d70fe2ce97b9db73b9c9b9c81fe3693640aec83416a966c446afea54acfae3c", size = 4023383, upload-time = "2026-05-06T06:17:53.947Z" }, + { url = "https://files.pythonhosted.org/packages/0f/6d/9563cfde59b5d8128a9c7ec972a087f4c782e4f7bac5a85234edfd5d5e49/hf_xet-1.5.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:73a0dae8c71de3b0633a45c73f4a4a5ed09e94b43441d82981a781d4f12baa42", size = 3792751, upload-time = "2026-05-06T06:17:51.791Z" }, + { url = "https://files.pythonhosted.org/packages/07/a5/ed5a0cf35b49a0571af5a8f53416dad1877a718c021c9937c3a53cb45781/hf_xet-1.5.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a60290ec57e9b71767fba7c3645ddafdd0759974b540441510c629c6db6db24a", size = 4456058, upload-time = "2026-05-06T06:17:40.735Z" }, + { url = "https://files.pythonhosted.org/packages/60/fb/3ae8bf2a7a37a4197d0195d7247fd25b3952e15cb8a599e285dfaa6f52b3/hf_xet-1.5.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:e5de0f6deada0dada870bb376a11bcd1f08abf3a968a6d118f33e72d1b1eb480", size = 4250783, upload-time = "2026-05-06T06:17:38.412Z" }, + { url = "https://files.pythonhosted.org/packages/a2/9b/8bae40d4d91525085137196e84eb0ed49cf65b5e96e5c3ecdadd8bd0fac2/hf_xet-1.5.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c799d49f1a5544a0ef7591c0ee75e0d6b93d6f56dc7a4979f59f7518d2872216", size = 4445594, upload-time = "2026-05-06T06:18:04.219Z" }, + { url = "https://files.pythonhosted.org/packages/13/59/c74efbbd4e8728172b2cc72a2bc014d2947a4b7bdced932fbd3f5da1a4e5/hf_xet-1.5.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2baea1b0b989e5c152fe81425f7745ddc8901280ba3d97c98d8cdece7b706c60", size = 4663995, upload-time = "2026-05-06T06:18:06.1Z" }, + { url = "https://files.pythonhosted.org/packages/73/32/8e1e0410af64cda9b139d1dcebdc993a8ff9c8c7c0e2696ae356d75ccc0d/hf_xet-1.5.0-cp313-cp313t-win_amd64.whl", hash = "sha256:526345b3ed45f374f6317349df489167606736c876241ba984105afe7fd4839d", size = 3966608, upload-time = "2026-05-06T06:18:19.74Z" }, + { url = "https://files.pythonhosted.org/packages/fc/34/a8febc8f4edbea8b3e21b02ebc8b628679b84ba7e45cde624a7736b51500/hf_xet-1.5.0-cp313-cp313t-win_arm64.whl", hash = "sha256:786d28e2eb8315d5035544b9d137b4a842d600c434bb91bf7d0d953cce906ad4", size = 3796946, upload-time = "2026-05-06T06:18:17.568Z" }, + { url = "https://files.pythonhosted.org/packages/2a/20/8fc8996afe5815fa1a6be8e9e5c02f24500f409d599e905800d498a4e14d/hf_xet-1.5.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:872d5601e6deea30d15865ede55d29eac6daf5a534ab417b99b6ef6b076dd96c", size = 4023495, upload-time = "2026-05-06T06:18:01.94Z" }, + { url = "https://files.pythonhosted.org/packages/32/6a/93d84463c00cecb561a7508aa6303e35ee2894294eac14245526924415fe/hf_xet-1.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9929561f5abf4581c8ea79587881dfef6b8abb2a0d8a51915936fc2a614f4e73", size = 3792731, upload-time = "2026-05-06T06:18:00.021Z" }, + { url = "https://files.pythonhosted.org/packages/9d/5a/8ec8e0c863b382d00b3c2e2af6ded6b06371be617144a625903a6d562f4b/hf_xet-1.5.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f7b7bbae318e583a86fb21e5a4a175d6721d628a2874f4bd022d0e660c32a682", size = 4456738, upload-time = "2026-05-06T06:17:49.574Z" }, + { url = "https://files.pythonhosted.org/packages/c5/ca/f7effa1a67717da2bcc6b6c28f71c6ca648c77acaec4e2c32f40cbe16d85/hf_xet-1.5.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:cf7b2dc6f31a4ea754bb50f74cde482dcf5d366d184076d8530b9872787f3761", size = 4251622, upload-time = "2026-05-06T06:17:47.096Z" }, + { url = "https://files.pythonhosted.org/packages/65/f2/19247dba3e231cf77dec59ddfb878f00057635ff773d099c9b59d37812c3/hf_xet-1.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8dbcbab554c9ef158ef2c991545c3e970ddd8cc7acdcd0a78c5a41095dab4ded", size = 4445667, upload-time = "2026-05-06T06:18:11.983Z" }, + { url = "https://files.pythonhosted.org/packages/7f/64/6f116801a3bcfb6f59f5c251f48cadc47ea54026441c4a385079286a94fa/hf_xet-1.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5906bf7718d3636dc13402914736abe723492cb730f744834f5f5b67d3a12702", size = 4664619, upload-time = "2026-05-06T06:18:13.771Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e8/069542d37946ed08669b127e1496fa99e78196d71de8d41eda5e9f1b7a58/hf_xet-1.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:5f3dc2248fc01cc0a00cd392ab497f1ca373fcbc7e3f2da1f452480b384e839e", size = 3966802, upload-time = "2026-05-06T06:18:28.162Z" }, + { url = "https://files.pythonhosted.org/packages/f9/91/fc6fdec27b14d04e88c386ac0a0129732b53fa23f7c4a78f4b83a039c567/hf_xet-1.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:b285cea1b5bab46b758772716ba8d6854a1a0310fed1c249d678a8b38601e5a0", size = 3797168, upload-time = "2026-05-06T06:18:26.287Z" }, + { url = "https://files.pythonhosted.org/packages/3d/fb/69ff198a82cae7eb1a69fb84d93b3a3e4816564d76817fe541ddc96874eb/hf_xet-1.5.0-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:dad0dc84e941b8ba3c860659fe1fdc35c049d47cce293f003287757e971a8f56", size = 4030814, upload-time = "2026-05-06T06:17:57.933Z" }, + { url = "https://files.pythonhosted.org/packages/9b/ff/edcc2b40162bef3ff78e14ab637e5f3b89243d6aee72f5949d3bb6a5af83/hf_xet-1.5.0-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:fd6e5a9b0fdac4ed03ed45ef79254a655b1aaab514a02202617fbf643f5fdf7a", size = 3798444, upload-time = "2026-05-06T06:17:55.79Z" }, + { url = "https://files.pythonhosted.org/packages/49/4d/103f76b04310e5e57656696cc184690d20c466af0bca3ca88f8c8ea5d4f3/hf_xet-1.5.0-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3531b1823a0e6d77d80f9ed15ca0e00f0d115094f8ac033d5cae88f4564cc949", size = 4465986, upload-time = "2026-05-06T06:17:44.886Z" }, + { url = "https://files.pythonhosted.org/packages/c4/a2/546f47f464737b3edbab6f8ddb57f2599b93d2cbb66f06abb475ccb48651/hf_xet-1.5.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:9a0ee58cd18d5ea799f7ed11290bbccbe56bdd8b1d97ca74b9cc49a3945d7a3b", size = 4259865, upload-time = "2026-05-06T06:17:42.639Z" }, + { url = "https://files.pythonhosted.org/packages/95/7f/1be593c1f28613be2e196473481cd81bfc5910795e30a34e8f744f6cac4f/hf_xet-1.5.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1e60df5a42e9bed8628b6416af2cba4cba57ae9f02de226a06b020d98e1aab18", size = 4459835, upload-time = "2026-05-06T06:18:08.026Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b2/703569fc881f3284487e68cda7b42179978480da3c438042a6bbbb4a671c/hf_xet-1.5.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4b35549ce62601b84da4ff9b24d970032ace3d4430f52d91bcbb26c901d6c690", size = 4672414, upload-time = "2026-05-06T06:18:09.864Z" }, + { url = "https://files.pythonhosted.org/packages/af/37/1b6def445c567286b50aa3b33828158e135b1be44938dde59f11382a500c/hf_xet-1.5.0-cp37-abi3-win_amd64.whl", hash = "sha256:2806c7c17b4d23f8d88f7c4814f838c3b6150773fe339c20af23e1cfaf2797e4", size = 3977238, upload-time = "2026-05-06T06:18:23.621Z" }, + { url = "https://files.pythonhosted.org/packages/62/94/3b66b148778ee100dcfd69c2ca22b57b41b44d3063ceec934f209e9184ce/hf_xet-1.5.0-cp37-abi3-win_arm64.whl", hash = "sha256:b6c9df403040248c76d808d3e047d64db2d923bae593eb244c41e425cf6cd7be", size = 3806916, upload-time = "2026-05-06T06:18:21.7Z" }, ] [[package]] @@ -857,21 +917,23 @@ wheels = [ [[package]] name = "huggingface-hub" -version = "0.36.2" +version = "1.17.0" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "click" }, { name = "filelock" }, { name = "fsspec" }, - { name = "hf-xet", marker = "platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" }, + { name = "hf-xet", marker = "platform_machine == 'AMD64' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" }, + { name = "httpx" }, { name = "packaging" }, { name = "pyyaml" }, - { name = "requests" }, { name = "tqdm" }, + { name = "typer" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7c/b7/8cb61d2eece5fb05a83271da168186721c450eb74e3c31f7ef3169fa475b/huggingface_hub-0.36.2.tar.gz", hash = "sha256:1934304d2fb224f8afa3b87007d58501acfda9215b334eed53072dd5e815ff7a", size = 649782, upload-time = "2026-02-06T09:24:13.098Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/65/9826515abb600b5722bcf53f8b4a2fb58340b1f8bfcaee19f83561c13a44/huggingface_hub-1.17.0.tar.gz", hash = "sha256:fad842b6763ef70ebc3919665b1b9273645203185400a7d6c5eddc2323cc3435", size = 797082, upload-time = "2026-05-28T15:12:13.347Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/af/48ac8483240de756d2438c380746e7130d1c6f75802ef22f3c6d49982787/huggingface_hub-0.36.2-py3-none-any.whl", hash = "sha256:48f0c8eac16145dfce371e9d2d7772854a4f591bcb56c9cf548accf531d54270", size = 566395, upload-time = "2026-02-06T09:24:11.133Z" }, + { url = "https://files.pythonhosted.org/packages/02/28/d7cef5e477b855c25d415b8f57e5bc7347c7a90cad3acf1725d0c92ca294/huggingface_hub-1.17.0-py3-none-any.whl", hash = "sha256:3b8156d23118e87f6a587648bfbc04f04a12a757ccb4ed298b35c4ae638bf24c", size = 671546, upload-time = "2026-05-28T15:12:11.441Z" }, ] [[package]] @@ -985,9 +1047,9 @@ requires-dist = [ { name = "aiocache", specifier = ">=0.12.1,<1.0" }, { name = "fastapi", specifier = ">=0.95.2,<1.0" }, { name = "gunicorn", specifier = ">=21.1.0" }, - { name = "huggingface-hub", specifier = ">=0.20.1,<1.0" }, - { name = "insightface", specifier = ">=0.7.3,<1.0" }, - { name = "numpy", specifier = "<2.4.0" }, + { name = "huggingface-hub", specifier = ">=1.0,<2.0" }, + { name = "insightface", specifier = ">=0.7.3,<2.0" }, + { name = "numpy", specifier = ">=2.4.0,<3.0" }, { name = "onnxruntime", marker = "extra == 'armnn'", specifier = ">=1.23.2,<2" }, { name = "onnxruntime", marker = "extra == 'cpu'", specifier = ">=1.23.2,<2" }, { name = "onnxruntime", marker = "extra == 'rknn'", specifier = ">=1.23.2,<2" }, @@ -996,7 +1058,7 @@ requires-dist = [ { name = "onnxruntime-openvino", marker = "extra == 'openvino'", specifier = ">=1.24.1,<2" }, { name = "opencv-python-headless", specifier = ">=4.7.0.72,<5.0" }, { name = "orjson", specifier = ">=3.9.5" }, - { name = "pillow", specifier = ">=12.2,<12.3" }, + { name = "pillow", specifier = ">=12.2,<13" }, { name = "pydantic", specifier = ">=2.0.0,<3" }, { name = "pydantic-settings", specifier = ">=2.5.2,<3" }, { name = "python-multipart", specifier = ">=0.0.6,<1.0" }, @@ -1160,80 +1222,80 @@ wheels = [ [[package]] name = "librt" -version = "0.9.0" +version = "0.11.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/eb/6b/3d5c13fb3e3c4f43206c8f9dfed13778c2ed4f000bacaa0b7ce3c402a265/librt-0.9.0.tar.gz", hash = "sha256:a0951822531e7aee6e0dfb556b30d5ee36bbe234faf60c20a16c01be3530869d", size = 184368, upload-time = "2026-04-09T16:06:26.173Z" } +sdist = { url = "https://files.pythonhosted.org/packages/40/08/9e7f6b5d2b5bed6ad055cdd5925f192bb403a51280f86b56554d9d0699a2/librt-0.11.0.tar.gz", hash = "sha256:075dc3ef4458a278e0195cbf6ac9d38808d9b906c5a6c7f7f79c3888276a3fb1", size = 200139, upload-time = "2026-05-10T18:17:25.138Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e2/1e/2ec7afcebcf3efea593d13aee18bbcfdd3a243043d848ebf385055e9f636/librt-0.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:90904fac73c478f4b83f4ed96c99c8208b75e6f9a8a1910548f69a00f1eaa671", size = 67155, upload-time = "2026-04-09T16:04:42.933Z" }, - { url = "https://files.pythonhosted.org/packages/18/77/72b85afd4435268338ad4ec6231b3da8c77363f212a0227c1ff3b45e4d35/librt-0.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:789fff71757facc0738e8d89e3b84e4f0251c1c975e85e81b152cdaca927cc2d", size = 69916, upload-time = "2026-04-09T16:04:44.042Z" }, - { url = "https://files.pythonhosted.org/packages/27/fb/948ea0204fbe2e78add6d46b48330e58d39897e425560674aee302dca81c/librt-0.9.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1bf465d1e5b0a27713862441f6467b5ab76385f4ecf8f1f3a44f8aa3c695b4b6", size = 199635, upload-time = "2026-04-09T16:04:45.5Z" }, - { url = "https://files.pythonhosted.org/packages/ac/cd/894a29e251b296a27957856804cfd21e93c194aa131de8bb8032021be07e/librt-0.9.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f819e0c6413e259a17a7c0d49f97f405abadd3c2a316a3b46c6440b7dbbedbb1", size = 211051, upload-time = "2026-04-09T16:04:47.016Z" }, - { url = "https://files.pythonhosted.org/packages/18/8f/dcaed0bc084a35f3721ff2d081158db569d2c57ea07d35623ddaca5cfc8e/librt-0.9.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e0785c2fb4a81e1aece366aa3e2e039f4a4d7d21aaaded5227d7f3c703427882", size = 224031, upload-time = "2026-04-09T16:04:48.207Z" }, - { url = "https://files.pythonhosted.org/packages/03/44/88f6c1ed1132cd418601cc041fbd92fed28b3a09f39de81978e0822d13ff/librt-0.9.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:80b25c7b570a86c03b5da69e665809deb39265476e8e21d96a9328f9762f9990", size = 218069, upload-time = "2026-04-09T16:04:50.025Z" }, - { url = "https://files.pythonhosted.org/packages/a3/90/7d02e981c2db12188d82b4410ff3e35bfdb844b26aecd02233626f46af2b/librt-0.9.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d4d16b608a1c43d7e33142099a75cd93af482dadce0bf82421e91cad077157f4", size = 224857, upload-time = "2026-04-09T16:04:51.684Z" }, - { url = "https://files.pythonhosted.org/packages/ef/c3/c77e706b7215ca32e928d47535cf13dbc3d25f096f84ddf8fbc06693e229/librt-0.9.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:194fc1a32e1e21fe809d38b5faea66cc65eaa00217c8901fbdb99866938adbdb", size = 219865, upload-time = "2026-04-09T16:04:52.949Z" }, - { url = "https://files.pythonhosted.org/packages/52/d1/32b0c1a0eb8461c70c11656c46a29f760b7c7edf3c36d6f102470c17170f/librt-0.9.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:8c6bc1384d9738781cfd41d09ad7f6e8af13cfea2c75ece6bd6d2566cdea2076", size = 218451, upload-time = "2026-04-09T16:04:54.174Z" }, - { url = "https://files.pythonhosted.org/packages/74/d1/adfd0f9c44761b1d49b1bec66173389834c33ee2bd3c7fd2e2367f1942d4/librt-0.9.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:15cb151e52a044f06e54ac7f7b47adbfc89b5c8e2b63e1175a9d587c43e8942a", size = 241300, upload-time = "2026-04-09T16:04:55.452Z" }, - { url = "https://files.pythonhosted.org/packages/09/b0/9074b64407712f0003c27f5b1d7655d1438979155f049720e8a1abd9b1a1/librt-0.9.0-cp311-cp311-win32.whl", hash = "sha256:f100bfe2acf8a3689af9d0cc660d89f17286c9c795f9f18f7b62dd1a6b247ae6", size = 55668, upload-time = "2026-04-09T16:04:56.689Z" }, - { url = "https://files.pythonhosted.org/packages/24/19/40b77b77ce80b9389fb03971431b09b6b913911c38d412059e0b3e2a9ef2/librt-0.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:0b73e4266307e51c95e09c0750b7ec383c561d2e97d58e473f6f6a209952fbb8", size = 62976, upload-time = "2026-04-09T16:04:57.733Z" }, - { url = "https://files.pythonhosted.org/packages/70/9d/9fa7a64041e29035cb8c575af5f0e3840be1b97b4c4d9061e0713f171849/librt-0.9.0-cp311-cp311-win_arm64.whl", hash = "sha256:bc5518873822d2faa8ebdd2c1a4d7c8ef47b01a058495ab7924cb65bdbf5fc9a", size = 53502, upload-time = "2026-04-09T16:04:58.806Z" }, - { url = "https://files.pythonhosted.org/packages/bf/90/89ddba8e1c20b0922783cd93ed8e64f34dc05ab59c38a9c7e313632e20ff/librt-0.9.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9b3e3bc363f71bda1639a4ee593cb78f7fbfeacc73411ec0d4c92f00730010a4", size = 68332, upload-time = "2026-04-09T16:05:00.09Z" }, - { url = "https://files.pythonhosted.org/packages/a8/40/7aa4da1fb08bdeeb540cb07bfc8207cb32c5c41642f2594dbd0098a0662d/librt-0.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0a09c2f5869649101738653a9b7ab70cf045a1105ac66cbb8f4055e61df78f2d", size = 70581, upload-time = "2026-04-09T16:05:01.213Z" }, - { url = "https://files.pythonhosted.org/packages/48/ac/73a2187e1031041e93b7e3a25aae37aa6f13b838c550f7e0f06f66766212/librt-0.9.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5ca8e133d799c948db2ab1afc081c333a825b5540475164726dcbf73537e5c2f", size = 203984, upload-time = "2026-04-09T16:05:02.542Z" }, - { url = "https://files.pythonhosted.org/packages/5e/3d/23460d571e9cbddb405b017681df04c142fb1b04cbfce77c54b08e28b108/librt-0.9.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:603138ee838ee1583f1b960b62d5d0007845c5c423feb68e44648b1359014e27", size = 215762, upload-time = "2026-04-09T16:05:04.127Z" }, - { url = "https://files.pythonhosted.org/packages/de/1e/42dc7f8ab63e65b20640d058e63e97fd3e482c1edbda3570d813b4d0b927/librt-0.9.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f4003f70c56a5addd6aa0897f200dd59afd3bf7bcd5b3cce46dd21f925743bc2", size = 230288, upload-time = "2026-04-09T16:05:05.883Z" }, - { url = "https://files.pythonhosted.org/packages/dc/08/ca812b6d8259ad9ece703397f8ad5c03af5b5fedfce64279693d3ce4087c/librt-0.9.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:78042f6facfd98ecb25e9829c7e37cce23363d9d7c83bc5f72702c5059eb082b", size = 224103, upload-time = "2026-04-09T16:05:07.148Z" }, - { url = "https://files.pythonhosted.org/packages/b6/3f/620490fb2fa66ffd44e7f900254bc110ebec8dac6c1b7514d64662570e6f/librt-0.9.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a361c9434a64d70a7dbb771d1de302c0cc9f13c0bffe1cf7e642152814b35265", size = 232122, upload-time = "2026-04-09T16:05:08.386Z" }, - { url = "https://files.pythonhosted.org/packages/e9/83/12864700a1b6a8be458cf5d05db209b0d8e94ae281e7ec261dbe616597b4/librt-0.9.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:dd2c7e082b0b92e1baa4da28163a808672485617bc855cc22a2fd06978fa9084", size = 225045, upload-time = "2026-04-09T16:05:09.707Z" }, - { url = "https://files.pythonhosted.org/packages/fd/1b/845d339c29dc7dbc87a2e992a1ba8d28d25d0e0372f9a0a2ecebde298186/librt-0.9.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7e6274fd33fc5b2a14d41c9119629d3ff395849d8bcbc80cf637d9e8d2034da8", size = 227372, upload-time = "2026-04-09T16:05:10.942Z" }, - { url = "https://files.pythonhosted.org/packages/8d/fe/277985610269d926a64c606f761d58d3db67b956dbbf40024921e95e7fcb/librt-0.9.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5093043afb226ecfa1400120d1ebd4442b4f99977783e4f4f7248879009b227f", size = 248224, upload-time = "2026-04-09T16:05:12.254Z" }, - { url = "https://files.pythonhosted.org/packages/92/1b/ee486d244b8de6b8b5dbaefabe6bfdd4a72e08f6353edf7d16d27114da8d/librt-0.9.0-cp312-cp312-win32.whl", hash = "sha256:9edcc35d1cae9fd5320171b1a838c7da8a5c968af31e82ecc3dff30b4be0957f", size = 55986, upload-time = "2026-04-09T16:05:13.529Z" }, - { url = "https://files.pythonhosted.org/packages/89/7a/ba1737012308c17dc6d5516143b5dce9a2c7ba3474afd54e11f44a4d1ef3/librt-0.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:3cc2917258e131ae5f958a4d872e07555b51cb7466a43433218061c74ef33745", size = 63260, upload-time = "2026-04-09T16:05:14.68Z" }, - { url = "https://files.pythonhosted.org/packages/36/e4/01752c113da15127f18f7bf11142f5640038f062407a611c059d0036c6aa/librt-0.9.0-cp312-cp312-win_arm64.whl", hash = "sha256:90e6d5420fc8a300518d4d2288154ff45005e920425c22cbbfe8330f3f754bd9", size = 53694, upload-time = "2026-04-09T16:05:16.095Z" }, - { url = "https://files.pythonhosted.org/packages/5f/d7/1b3e26fffde1452d82f5666164858a81c26ebe808e7ae8c9c88628981540/librt-0.9.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f29b68cd9714531672db62cc54f6e8ff981900f824d13fa0e00749189e13778e", size = 68367, upload-time = "2026-04-09T16:05:17.243Z" }, - { url = "https://files.pythonhosted.org/packages/a5/5b/c61b043ad2e091fbe1f2d35d14795e545d0b56b03edaa390fa1dcee3d160/librt-0.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7d5c8a5929ac325729f6119802070b561f4db793dffc45e9ac750992a4ed4d22", size = 70595, upload-time = "2026-04-09T16:05:18.471Z" }, - { url = "https://files.pythonhosted.org/packages/a3/22/2448471196d8a73370aa2f23445455dc42712c21404081fcd7a03b9e0749/librt-0.9.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:756775d25ec8345b837ab52effee3ad2f3b2dfd6bbee3e3f029c517bd5d8f05a", size = 204354, upload-time = "2026-04-09T16:05:19.593Z" }, - { url = "https://files.pythonhosted.org/packages/ac/5e/39fc4b153c78cfd2c8a2dcb32700f2d41d2312aa1050513183be4540930d/librt-0.9.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b8f5d00b49818f4e2b1667db994488b045835e0ac16fe2f924f3871bd2b8ac5", size = 216238, upload-time = "2026-04-09T16:05:20.868Z" }, - { url = "https://files.pythonhosted.org/packages/d7/42/bc2d02d0fa7badfa63aa8d6dcd8793a9f7ef5a94396801684a51ed8d8287/librt-0.9.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c81aef782380f0f13ead670aae01825eb653b44b046aa0e5ebbb79f76ed4aa11", size = 230589, upload-time = "2026-04-09T16:05:22.305Z" }, - { url = "https://files.pythonhosted.org/packages/c8/7b/e2d95cc513866373692aa5edf98080d5602dd07cabfb9e5d2f70df2f25f7/librt-0.9.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:66b58fed90a545328e80d575467244de3741e088c1af928f0b489ebec3ef3858", size = 224610, upload-time = "2026-04-09T16:05:23.647Z" }, - { url = "https://files.pythonhosted.org/packages/31/d5/6cec4607e998eaba57564d06a1295c21b0a0c8de76e4e74d699e627bd98c/librt-0.9.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e78fb7419e07d98c2af4b8567b72b3eaf8cb05caad642e9963465569c8b2d87e", size = 232558, upload-time = "2026-04-09T16:05:25.025Z" }, - { url = "https://files.pythonhosted.org/packages/95/8c/27f1d8d3aaf079d3eb26439bf0b32f1482340c3552e324f7db9dca858671/librt-0.9.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c3786f0f4490a5cd87f1ed6cefae833ad6b1060d52044ce0434a2e85893afd0", size = 225521, upload-time = "2026-04-09T16:05:26.311Z" }, - { url = "https://files.pythonhosted.org/packages/6b/d8/1e0d43b1c329b416017619469b3c3801a25a6a4ef4a1c68332aeaa6f72ca/librt-0.9.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:8494cfc61e03542f2d381e71804990b3931175a29b9278fdb4a5459948778dc2", size = 227789, upload-time = "2026-04-09T16:05:27.624Z" }, - { url = "https://files.pythonhosted.org/packages/2c/b4/d3d842e88610fcd4c8eec7067b0c23ef2d7d3bff31496eded6a83b0f99be/librt-0.9.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:07cf11f769831186eeac424376e6189f20ace4f7263e2134bdb9757340d84d4d", size = 248616, upload-time = "2026-04-09T16:05:29.181Z" }, - { url = "https://files.pythonhosted.org/packages/ec/28/527df8ad0d1eb6c8bdfa82fc190f1f7c4cca5a1b6d7b36aeabf95b52d74d/librt-0.9.0-cp313-cp313-win32.whl", hash = "sha256:850d6d03177e52700af605fd60db7f37dcb89782049a149674d1a9649c2138fd", size = 56039, upload-time = "2026-04-09T16:05:30.709Z" }, - { url = "https://files.pythonhosted.org/packages/f3/a7/413652ad0d92273ee5e30c000fc494b361171177c83e57c060ecd3c21538/librt-0.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:a5af136bfba820d592f86c67affcef9b3ff4d4360ac3255e341e964489b48519", size = 63264, upload-time = "2026-04-09T16:05:31.881Z" }, - { url = "https://files.pythonhosted.org/packages/a4/0a/92c244309b774e290ddb15e93363846ae7aa753d9586b8aad511c5e6145b/librt-0.9.0-cp313-cp313-win_arm64.whl", hash = "sha256:4c4d0440a3a8e31d962340c3e1cc3fc9ee7febd34c8d8f770d06adb947779ea5", size = 53728, upload-time = "2026-04-09T16:05:33.31Z" }, - { url = "https://files.pythonhosted.org/packages/cd/c1/184e539543f06ea2912f4b92a5ffaede4f9b392689e3f00acbf8134bee92/librt-0.9.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:3f05d145df35dca5056a8bc3838e940efebd893a54b3e19b2dda39ceaa299bcb", size = 67830, upload-time = "2026-04-09T16:05:34.517Z" }, - { url = "https://files.pythonhosted.org/packages/f3/ad/23399bdcb7afca819acacdef31b37ee59de261bd66b503a7995c03c4b0dc/librt-0.9.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1c587494461ebd42229d0f1739f3aa34237dd9980623ecf1be8d3bcba79f4499", size = 70280, upload-time = "2026-04-09T16:05:35.649Z" }, - { url = "https://files.pythonhosted.org/packages/9f/0b/4542dc5a2b8772dbf92cafb9194701230157e73c14b017b6961a23598b03/librt-0.9.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b0a2040f801406b93657a70b72fa12311063a319fee72ce98e1524da7200171f", size = 201925, upload-time = "2026-04-09T16:05:36.739Z" }, - { url = "https://files.pythonhosted.org/packages/31/d4/8ee7358b08fd0cfce051ef96695380f09b3c2c11b77c9bfbc367c921cce5/librt-0.9.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f38bc489037eca88d6ebefc9c4d41a4e07c8e8b4de5188a9e6d290273ad7ebb1", size = 212381, upload-time = "2026-04-09T16:05:38.043Z" }, - { url = "https://files.pythonhosted.org/packages/f2/94/a2025fe442abedf8b038038dab3dba942009ad42b38ea064a1a9e6094241/librt-0.9.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3fd278f5e6bf7c75ccd6d12344eb686cc020712683363b66f46ac79d37c799f", size = 227065, upload-time = "2026-04-09T16:05:39.394Z" }, - { url = "https://files.pythonhosted.org/packages/7c/e9/b9fcf6afa909f957cfbbf918802f9dada1bd5d3c1da43d722fd6a310dc3f/librt-0.9.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fcbdf2a9ca24e87bbebb47f1fe34e531ef06f104f98c9ccfc953a3f3344c567a", size = 221333, upload-time = "2026-04-09T16:05:40.999Z" }, - { url = "https://files.pythonhosted.org/packages/ac/7c/ba54cd6aa6a3c8cd12757a6870e0c79a64b1e6327f5248dcff98423f4d43/librt-0.9.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e306d956cfa027fe041585f02a1602c32bfa6bb8ebea4899d373383295a6c62f", size = 229051, upload-time = "2026-04-09T16:05:42.605Z" }, - { url = "https://files.pythonhosted.org/packages/4b/4b/8cfdbad314c8677a0148bf0b70591d6d18587f9884d930276098a235461b/librt-0.9.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:465814ab157986acb9dfa5ccd7df944be5eefc0d08d31ec6e8d88bc71251d845", size = 222492, upload-time = "2026-04-09T16:05:43.842Z" }, - { url = "https://files.pythonhosted.org/packages/1f/d1/2eda69563a1a88706808decdce035e4b32755dbfbb0d05e1a65db9547ed1/librt-0.9.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:703f4ae36d6240bfe24f542bac784c7e4194ec49c3ba5a994d02891649e2d85b", size = 223849, upload-time = "2026-04-09T16:05:45.054Z" }, - { url = "https://files.pythonhosted.org/packages/04/44/b2ed37df6be5b3d42cfe36318e0598e80843d5c6308dd63d0bf4e0ce5028/librt-0.9.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3be322a15ee5e70b93b7a59cfd074614f22cc8c9ff18bd27f474e79137ea8d3b", size = 245001, upload-time = "2026-04-09T16:05:46.34Z" }, - { url = "https://files.pythonhosted.org/packages/47/e7/617e412426df89169dd2a9ed0cc8752d5763336252c65dbf945199915119/librt-0.9.0-cp314-cp314-win32.whl", hash = "sha256:b8da9f8035bb417770b1e1610526d87ad4fc58a2804dc4d79c53f6d2cf5a6eb9", size = 51799, upload-time = "2026-04-09T16:05:47.738Z" }, - { url = "https://files.pythonhosted.org/packages/24/ed/c22ca4db0ca3cbc285e4d9206108746beda561a9792289c3c31281d7e9df/librt-0.9.0-cp314-cp314-win_amd64.whl", hash = "sha256:b8bd70d5d816566a580d193326912f4a76ec2d28a97dc4cd4cc831c0af8e330e", size = 59165, upload-time = "2026-04-09T16:05:49.198Z" }, - { url = "https://files.pythonhosted.org/packages/24/56/875398fafa4cbc8f15b89366fc3287304ddd3314d861f182a4b87595ace0/librt-0.9.0-cp314-cp314-win_arm64.whl", hash = "sha256:fc5758e2b7a56532dc33e3c544d78cbaa9ecf0a0f2a2da2df882c1d6b99a317f", size = 49292, upload-time = "2026-04-09T16:05:50.362Z" }, - { url = "https://files.pythonhosted.org/packages/4c/61/bc448ecbf9b2d69c5cff88fe41496b19ab2a1cbda0065e47d4d0d51c0867/librt-0.9.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:f24b90b0e0c8cc9491fb1693ae91fe17cb7963153a1946395acdbdd5818429a4", size = 70175, upload-time = "2026-04-09T16:05:51.564Z" }, - { url = "https://files.pythonhosted.org/packages/60/f2/c47bb71069a73e2f04e70acbd196c1e5cc411578ac99039a224b98920fd4/librt-0.9.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3fe56e80badb66fdcde06bef81bbaa5bfcf6fbd7aefb86222d9e369c38c6b228", size = 72951, upload-time = "2026-04-09T16:05:52.699Z" }, - { url = "https://files.pythonhosted.org/packages/29/19/0549df59060631732df758e8886d92088da5fdbedb35b80e4643664e8412/librt-0.9.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:527b5b820b47a09e09829051452bb0d1dd2122261254e2a6f674d12f1d793d54", size = 225864, upload-time = "2026-04-09T16:05:53.895Z" }, - { url = "https://files.pythonhosted.org/packages/9d/f8/3b144396d302ac08e50f89e64452c38db84bc7b23f6c60479c5d3abd303c/librt-0.9.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d429bdd4ac0ab17c8e4a8af0ed2a7440b16eba474909ab357131018fe8c7e71", size = 241155, upload-time = "2026-04-09T16:05:55.191Z" }, - { url = "https://files.pythonhosted.org/packages/7a/ce/ee67ec14581de4043e61d05786d2aed6c9b5338816b7859bcf07455c6a9f/librt-0.9.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7202bdcac47d3a708271c4304a474a8605a4a9a4a709e954bf2d3241140aa938", size = 252235, upload-time = "2026-04-09T16:05:56.549Z" }, - { url = "https://files.pythonhosted.org/packages/8a/fa/0ead15daa2b293a54101550b08d4bafe387b7d4a9fc6d2b985602bae69b6/librt-0.9.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0d620e74897f8c2613b3c4e2e9c1e422eb46d2ddd07df540784d44117836af3", size = 244963, upload-time = "2026-04-09T16:05:57.858Z" }, - { url = "https://files.pythonhosted.org/packages/29/68/9fbf9a9aa704ba87689e40017e720aced8d9a4d2b46b82451d8142f91ec9/librt-0.9.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d69fc39e627908f4c03297d5a88d9284b73f4d90b424461e32e8c2485e21c283", size = 257364, upload-time = "2026-04-09T16:05:59.686Z" }, - { url = "https://files.pythonhosted.org/packages/1a/8d/9d60869f1b6716c762e45f66ed945b1e5dd649f7377684c3b176ae424648/librt-0.9.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:c2640e23d2b7c98796f123ffd95cf2022c7777aa8a4a3b98b36c570d37e85eee", size = 247661, upload-time = "2026-04-09T16:06:00.938Z" }, - { url = "https://files.pythonhosted.org/packages/70/ff/a5c365093962310bfdb4f6af256f191085078ffb529b3f0cbebb5b33ebe2/librt-0.9.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:451daa98463b7695b0a30aa56bf637831ea559e7b8101ac2ef6382e8eb15e29c", size = 248238, upload-time = "2026-04-09T16:06:02.537Z" }, - { url = "https://files.pythonhosted.org/packages/a0/3c/2d34365177f412c9e19c0a29f969d70f5343f27634b76b765a54d8b27705/librt-0.9.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:928bd06eca2c2bbf4349e5b817f837509b0604342e65a502de1d50a7570afd15", size = 269457, upload-time = "2026-04-09T16:06:03.833Z" }, - { url = "https://files.pythonhosted.org/packages/bc/cd/de45b239ea3bdf626f982a00c14bfcf2e12d261c510ba7db62c5969a27cd/librt-0.9.0-cp314-cp314t-win32.whl", hash = "sha256:a9c63e04d003bc0fb6a03b348018b9a3002f98268200e22cc80f146beac5dc40", size = 52453, upload-time = "2026-04-09T16:06:05.229Z" }, - { url = "https://files.pythonhosted.org/packages/7f/f9/bfb32ae428aa75c0c533915622176f0a17d6da7b72b5a3c6363685914f70/librt-0.9.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f162af66a2ed3f7d1d161a82ca584efd15acd9c1cff190a373458c32f7d42118", size = 60044, upload-time = "2026-04-09T16:06:06.398Z" }, - { url = "https://files.pythonhosted.org/packages/aa/47/7d70414bcdbb3bc1f458a8d10558f00bbfdb24e5a11740fc8197e12c3255/librt-0.9.0-cp314-cp314t-win_arm64.whl", hash = "sha256:a4b25c6c25cac5d0d9d6d6da855195b254e0021e513e0249f0e3b444dc6e0e61", size = 50009, upload-time = "2026-04-09T16:06:07.995Z" }, + { url = "https://files.pythonhosted.org/packages/fe/87/2bf31fe17587b29e3f93ec31421e2b1e1c3e349b8bf6c7c313dbad1d5340/librt-0.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:93d95bd45b7d58343d8b90d904450a545144eec19a002511163426f8ab1fae29", size = 141092, upload-time = "2026-05-10T18:15:34.795Z" }, + { url = "https://files.pythonhosted.org/packages/cf/08/5c5bf772920b7ebac6e32bc91a643e0ab3870199c0b542356d3baa83970a/librt-0.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ee278c769a713638cdacd4c0436d72156e75df3ebc0166ab2b9dc43acc386c9", size = 142035, upload-time = "2026-05-10T18:15:36.242Z" }, + { url = "https://files.pythonhosted.org/packages/06/20/662a03d254e5b000d838e8b345d83303ddb768c080fd488e40634c0fa66b/librt-0.11.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f230cb1cbc9faaa616f9a678f530ebcf186e414b6bcbd88b960e4ba1b92428d5", size = 475022, upload-time = "2026-05-10T18:15:37.56Z" }, + { url = "https://files.pythonhosted.org/packages/de/f3/aa81523e45184c6ec23dc7f63263362ec55f80a09d424c012359ecbe7e35/librt-0.11.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:5d63c855d86938d9de93e265c9bd8c705b51ec494de5738340ee93767a686e4b", size = 467273, upload-time = "2026-05-10T18:15:39.182Z" }, + { url = "https://files.pythonhosted.org/packages/6b/6f/59c74b560ca8853834d5501d589c8a2519f4184f273a085ffd0f37a1cc47/librt-0.11.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:993f028be9e96a08d31df3479ac80d99be374d17f3b78e4796b3fd3c913d4e89", size = 497083, upload-time = "2026-05-10T18:15:40.634Z" }, + { url = "https://files.pythonhosted.org/packages/fe/7b/5aa4d2c9600a719401160bf7055417df0b2a47439b9d88286ce45e56b65f/librt-0.11.0-cp311-cp311-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:258d73a0aa66a055e65b2e4d1b8cdb23b9d132c5bb915d9547d804fcaed116cc", size = 489139, upload-time = "2026-05-10T18:15:41.934Z" }, + { url = "https://files.pythonhosted.org/packages/d6/31/9143803d7da6856a69153785768c4936864430eec0fd9461c3ea527d9922/librt-0.11.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0827efe7854718f04aaddf6496e96960a956e676fe1d0f04eb41511fd8ad06d5", size = 508442, upload-time = "2026-05-10T18:15:43.206Z" }, + { url = "https://files.pythonhosted.org/packages/2f/5a/bce08184488426bda4ccc2c4964ac048c8f68ae89bd7120082eef4233cfd/librt-0.11.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7753e57d6e12d019c0d8786f1c09c709f4c3fcc57c3887b24e36e6c06ec938b7", size = 514230, upload-time = "2026-05-10T18:15:44.761Z" }, + { url = "https://files.pythonhosted.org/packages/89/8c/bb5e213d254b7505a0e658da199d8ab719086632ce09eef311ab27976523/librt-0.11.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:11bd19822431cc21af9f27374e7ae2e58103c7d98bda823536a6c47f6bb2bb3d", size = 494231, upload-time = "2026-05-10T18:15:46.308Z" }, + { url = "https://files.pythonhosted.org/packages/9d/fb/541cdad5b1ab1300398c74c4c9a497b88e5074c21b1244c8f49731d3a284/librt-0.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:22bdf239b219d3993761a148ffa134b19e52e9989c84f845d5d7b71d70a17412", size = 537585, upload-time = "2026-05-10T18:15:47.629Z" }, + { url = "https://files.pythonhosted.org/packages/8f/f2/464bb69295c320cb06bddb4f14a4ec67934ee14b2bffb12b19fb7ab287ba/librt-0.11.0-cp311-cp311-win32.whl", hash = "sha256:46c60b61e308eb535fbd6fa622b1ee1bb2815691c1ad9c98bf7b84952ec3bc8d", size = 100509, upload-time = "2026-05-10T18:15:49.157Z" }, + { url = "https://files.pythonhosted.org/packages/6d/e7/a17ee1788f9e4fbf548c19f4afa07c92089b9e24fef6cb2410863781ef4c/librt-0.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:902e546ff044f579ff1c953ff5fce97b636fe9e3943996b2177710c6ef076f73", size = 118628, upload-time = "2026-05-10T18:15:50.345Z" }, + { url = "https://files.pythonhosted.org/packages/cc/c7/6c766214f9f9903bcfcfbef97d807af8d8f5aa3502d247858ab17582d212/librt-0.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:65ac3bc20f78aa0ee5ae84baa68917f89fef4af63e941084dd019a0d0e749f0c", size = 103122, upload-time = "2026-05-10T18:15:52.068Z" }, + { url = "https://files.pythonhosted.org/packages/8b/d0/07c77e067f0838949b43bd89232c29d72efebb9d2801a9750184eb706b71/librt-0.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b87504f1690a23b9a2cca841191a04f83895d4fc2dd04df91d82b1a04ca2ad46", size = 144147, upload-time = "2026-05-10T18:15:53.227Z" }, + { url = "https://files.pythonhosted.org/packages/7a/24/8493538fa4f62f982686398a5b8f68008138a75086abdea19ade64bf4255/librt-0.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40071fc5fe0ce8daa6de616702314a01e1250711682b0523d6ab8d4525910cb3", size = 143614, upload-time = "2026-05-10T18:15:54.657Z" }, + { url = "https://files.pythonhosted.org/packages/ff/1e/f8bad050810d9171f34a1648ed910e56814c2ba61639f2bd53c6377ae24b/librt-0.11.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:137e79445c896a0ea7b265f52d23954e05b64222ee1af69e2cb34219067cbb67", size = 485538, upload-time = "2026-05-10T18:15:56.117Z" }, + { url = "https://files.pythonhosted.org/packages/c0/fe/3594ebfbaf03084ba4b120c9ba5c3183fd938a48725e9bbe6ff0a5159ad8/librt-0.11.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:cca6644054e78746d8d4ef238681f9c34ff8b584fe6b988ecebb8db3b15e622a", size = 479623, upload-time = "2026-05-10T18:15:57.544Z" }, + { url = "https://files.pythonhosted.org/packages/b0/da/5d1876984b3746c85dbd219dbfcb73c85f54ee263fd32e5b2a632ec14571/librt-0.11.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5b0eea49f5562861ee8d757a32ef7d559c1d35be2aaaa1ec28941d74c9ffc8a", size = 513082, upload-time = "2026-05-10T18:15:58.805Z" }, + { url = "https://files.pythonhosted.org/packages/19/6e/55bdf5d5ca00c3e18430690bf2c953d8d3ffd3c337418173d33dec985dc9/librt-0.11.0-cp312-cp312-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0d1029d7e1ae1a7e647ed6fb5df8c4ce2dffefb7a9f5fd1376a4554d96dac09f", size = 508105, upload-time = "2026-05-10T18:16:00.2Z" }, + { url = "https://files.pythonhosted.org/packages/07/10/f1f23a7c595ee90ece4d35c851e5d104b1311a887ed1b4ac4c35bbd13da8/librt-0.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bc3ce6b33c5828d9e80592011a5c584cb2ce86edbc4088405f70da47dc1d1b3b", size = 522268, upload-time = "2026-05-10T18:16:01.708Z" }, + { url = "https://files.pythonhosted.org/packages/b6/02/5720f5697a7f54b78b3aefbe20df3a48cedcff1276618c4aa481177942ed/librt-0.11.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:936c5995f3514a42111f20099397d8177c79b4d7e70961e396c6f5a0a3566766", size = 527348, upload-time = "2026-05-10T18:16:03.496Z" }, + { url = "https://files.pythonhosted.org/packages/50/db/b4a47c6f91db4ff76348a0b3dd0cc65e090a078b765a810a62ff9434c3d3/librt-0.11.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:9bc0ca6ad9381cbe8e4aa6e5726e4c80c78115a6e9723c599ed1d73e092bc49d", size = 516294, upload-time = "2026-05-10T18:16:05.173Z" }, + { url = "https://files.pythonhosted.org/packages/9e/58/9384b2f4eb1ed1d273d40948a7c5c4b2360213b402ef3be4641c06299f9c/librt-0.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:070aa8c26c0a74774317a72df8851facc7f0f012a5b406557ac56992d92e1ec8", size = 553608, upload-time = "2026-05-10T18:16:06.839Z" }, + { url = "https://files.pythonhosted.org/packages/21/7b/5aa8848a7c6a9278c79375146da1812e695754ceec5f005e6043461a7315/librt-0.11.0-cp312-cp312-win32.whl", hash = "sha256:6bf14feb84b05ae945277395451998c89c54d0def4070eb5c08de544930b245a", size = 101879, upload-time = "2026-05-10T18:16:08.103Z" }, + { url = "https://files.pythonhosted.org/packages/37/33/8a745436944947575b584231750a41417de1a38cf6a2e9251d1065651c09/librt-0.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:75672f0bc524ede266287d532d7923dbce94c7514ad07627bac3d0c6d92cc4d9", size = 119831, upload-time = "2026-05-10T18:16:09.174Z" }, + { url = "https://files.pythonhosted.org/packages/59/67/a6739ac96e28b7855808bdb0370e250606104a859750d209e5a0716fe7ab/librt-0.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:2f10cf143e4a9bb0f4f5af568a00df94a2d69ef41c2579584454bb0fe5cc642c", size = 103470, upload-time = "2026-05-10T18:16:10.369Z" }, + { url = "https://files.pythonhosted.org/packages/82/61/e59168d4d0bf2bf90f4f0caf7a001bfc60254c3af4586013b04dc3ef517b/librt-0.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:78dc31f7fdfe9c9d0eb0e8f42d139db230e826415bbcabd9f0e9faaaee909894", size = 144119, upload-time = "2026-05-10T18:16:11.771Z" }, + { url = "https://files.pythonhosted.org/packages/61/fd/caa1d60b12f7dd79ccea23054e06eeaebe266a5f52c40a6b651069200ce5/librt-0.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fa475675db22290c3158e1d42326d0f5a65f04f44a0e68c3630a25b53560fb9c", size = 143565, upload-time = "2026-05-10T18:16:13.334Z" }, + { url = "https://files.pythonhosted.org/packages/b8/a9/dc744f5c2b4978d48db970be29f22716d3413d28b14ad99740817315cf2c/librt-0.11.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:621db29691044bdeda22e789e482e1b0f3a985d90e3426c9c6d17606416205ea", size = 485395, upload-time = "2026-05-10T18:16:14.729Z" }, + { url = "https://files.pythonhosted.org/packages/8f/21/7f8e97a1e4dae952a5a95948f6f8507a173bc1e669f54340bba6ca1ca31b/librt-0.11.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:a9010e2ed5b3a9e158c5fd966b3ab7e834bb3d3aacc8f66c91dd4b57a3799230", size = 479383, upload-time = "2026-05-10T18:16:16.321Z" }, + { url = "https://files.pythonhosted.org/packages/a6/6d/d8ee9c114bebf2c50e29ec2aa940826fccb62a645c3e4c18760987d0e16d/librt-0.11.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7c39513d8b7477a2e1ed8c43fc21c524e8d5a0f8d4e8b7b074dbdbe7820a08e2", size = 513010, upload-time = "2026-05-10T18:16:17.647Z" }, + { url = "https://files.pythonhosted.org/packages/f0/43/0b5708af2bd30a46400e72ba6bdaa8f066f15fb9a688527e34220e8d6c06/librt-0.11.0-cp313-cp313-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7aef3cf1d5af86e770ab04bfd993dfc4ae8b8c17f66fb77dd4a7d50de7bbb1a3", size = 508433, upload-time = "2026-05-10T18:16:19.309Z" }, + { url = "https://files.pythonhosted.org/packages/4a/50/356187247d09013490481033183b3532b58acf8028bcb34b2b56a375c9b2/librt-0.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:557183ddc36babe46b27dd60facbd5adb4492181a5be887587d57cda6e092f21", size = 522595, upload-time = "2026-05-10T18:16:20.642Z" }, + { url = "https://files.pythonhosted.org/packages/40/e7/c6ac4240899c7f3248079d5a9900debe0dadb3fdeaf856684c987105ba47/librt-0.11.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:83d3e1f72bd42f6c5c0b7daec530c3f829bd02db42c70b8ddf0c2d90a2459930", size = 527255, upload-time = "2026-05-10T18:16:22.352Z" }, + { url = "https://files.pythonhosted.org/packages/eb/b5/a81322dbeedeeaf9c1ee6f001734d28a09d8383ac9e6779bc24bbd0743c6/librt-0.11.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:4ce1f21fbe589bc1afd7872dece84fb0e1144f794a288e58a10d2c54a55c43be", size = 516847, upload-time = "2026-05-10T18:16:23.627Z" }, + { url = "https://files.pythonhosted.org/packages/ae/66/6e6323787d592b55204a42595ff1102da5115601b53a7e9ddebc889a6da5/librt-0.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:970b09f7044ea2b64c9da42fd3d335666518cfd1c6e8a182c95da73d0214b41e", size = 553920, upload-time = "2026-05-10T18:16:25.025Z" }, + { url = "https://files.pythonhosted.org/packages/9c/21/623f8ca230857102066d9ca8c6c1734995908c4d0d1bee7bb2ef0021cb33/librt-0.11.0-cp313-cp313-win32.whl", hash = "sha256:78fddc31cd4d3caa897ad5d31f856b1faadc9474021ad6cb182b9018793e254e", size = 101898, upload-time = "2026-05-10T18:16:26.649Z" }, + { url = "https://files.pythonhosted.org/packages/b3/1d/b4ebd44dd723f768469007515cb92251e0ae286c94c140f374801140fa74/librt-0.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:8ca8aa88751a775870b764e93bad5135385f563cb8dcee399abf034ea4d3cb47", size = 119812, upload-time = "2026-05-10T18:16:27.859Z" }, + { url = "https://files.pythonhosted.org/packages/3b/e4/b2f4ca7965ca373b491cdb4bc25cdb30c1649ca81a8782056a83850292a9/librt-0.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:96f044bb325fd9cf1a723015638c219e9143f0dfbc0ca54c565df2b7fc748b44", size = 103448, upload-time = "2026-05-10T18:16:29.066Z" }, + { url = "https://files.pythonhosted.org/packages/29/eb/dbce197da4e227779e56b5735f2decc3eb36e55a1cdbf1bd65d6639d76c1/librt-0.11.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4a017a95e5837dc15a8c5661d60e05daa96b90908b1aa6b7acdf443cd25c8ebd", size = 143345, upload-time = "2026-05-10T18:16:30.674Z" }, + { url = "https://files.pythonhosted.org/packages/76/a3/254bebd0c11c8ba684018efb8006ff22e466abce445215cca6c778e7d9de/librt-0.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b1ecbd9819deccc39b7542bf4d2a740d8a620694d39989e58661d3763458f8d4", size = 143131, upload-time = "2026-05-10T18:16:32.037Z" }, + { url = "https://files.pythonhosted.org/packages/f1/3f/f77d6122d21ac7bf6ae8a7dfced1bd2a7ac545d3273ebdcaf8042f6d619f/librt-0.11.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7da327dacd7be8f8ec36547373550744a3cc0e536d54665cd83f8bcd961200e8", size = 477024, upload-time = "2026-05-10T18:16:33.493Z" }, + { url = "https://files.pythonhosted.org/packages/ac/0a/2c996dadebaa7d9bbbd43ef2d4f3e66b6da545f838a41694ef6172cebec8/librt-0.11.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:0dc56b1f8d06e60db362cc3fdae206681817f86ce4725d34511473487f12a34b", size = 474221, upload-time = "2026-05-10T18:16:34.864Z" }, + { url = "https://files.pythonhosted.org/packages/0a/7e/f5d92af8486b8272c23b3e686b46ff72d89c8169585eb61eef01a2ac7147/librt-0.11.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05fb8fb2ab90e21c8d12ea240d744ad514da9baf381ebfa70d91d20d21713175", size = 505174, upload-time = "2026-05-10T18:16:36.705Z" }, + { url = "https://files.pythonhosted.org/packages/af/1a/cb0734fe86398eb33193ab753b7326255c74cac5eb09e76b9b16536e7adb/librt-0.11.0-cp314-cp314-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cae74872be221df4374d10fec61f93ed1513b9546ea84f2c0bf73ab3e9bd0b03", size = 497216, upload-time = "2026-05-10T18:16:38.418Z" }, + { url = "https://files.pythonhosted.org/packages/18/06/094820f91558b66e29943c0ec41c9914f460f48dd51fc503c3101e10842d/librt-0.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:32bcc918c0148eb7e3d57385125bac7e5f9e4359d05f07448b09f6f778c2f31c", size = 513921, upload-time = "2026-05-10T18:16:39.848Z" }, + { url = "https://files.pythonhosted.org/packages/0b/c2/00de9018871a282f530cacb457d5ec0428f6ac7e6fedde9aff7468d9fb04/librt-0.11.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:f9743fc99135d5f78d2454435615f6dec0473ca507c26ce9d92b10b562a280d3", size = 520850, upload-time = "2026-05-10T18:16:41.471Z" }, + { url = "https://files.pythonhosted.org/packages/51/9d/64631832348fd1834fb3a61b996434edddaaf25a31d03b0a76273159d2cf/librt-0.11.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:5ba067f4aadae8fda802d91d2124c90c42195ff32d9161d3549e6d05cfe26f96", size = 504237, upload-time = "2026-05-10T18:16:43.15Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ec/ae5525eb16edc827a044e7bb8777a455ff95d4bca9379e7e6bddd7383647/librt-0.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:de3bf945454d032f9e390b85c4072e0a0570bf825421c8be0e71209fa65e1abe", size = 546261, upload-time = "2026-05-10T18:16:44.408Z" }, + { url = "https://files.pythonhosted.org/packages/5a/09/adce371f27ca039411da9659f7430fcc2ba6cd0c7b3e4467a0f091be7fa9/librt-0.11.0-cp314-cp314-win32.whl", hash = "sha256:d2277a05f6dcb9fd13db9566aac4fabd68c3ea1ea46ee5567d4eef8efa495a2f", size = 96965, upload-time = "2026-05-10T18:16:46.039Z" }, + { url = "https://files.pythonhosted.org/packages/d6/ee/8ac720d98548f173c7ce2e632a7ca94673f74cacd5c8162a84af5b35958a/librt-0.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:ab73e8db5e3f564d812c1f5c3a175930a5f9bc96ccb5e3b22a34d7858b401cf7", size = 115151, upload-time = "2026-05-10T18:16:47.133Z" }, + { url = "https://files.pythonhosted.org/packages/94/20/c900cf14efeb09b6bef2b2dff20779f73464b97fd58d1c6bccc379588ae3/librt-0.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:aea3caa317752e3a466fa8af45d91ee0ea8c7fdd96e42b0a8dd9b76a7931eba1", size = 98850, upload-time = "2026-05-10T18:16:48.597Z" }, + { url = "https://files.pythonhosted.org/packages/0c/71/944bfe4b64e12abffcd3c15e1cce07f72f3d55655083786285f4dedeb532/librt-0.11.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d1b36540d7aaf9b9101b3a6f376c8d8e9f7a9aec93ed05918f2c69d493ffef72", size = 151138, upload-time = "2026-05-10T18:16:49.839Z" }, + { url = "https://files.pythonhosted.org/packages/b6/10/99e64a5c86989357fda078c8143c533389585f6473b7439172dd8f3b3b2d/librt-0.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:efbb343ab2ce3540f4ecbe6315d677ed70f37cd9a72b1e58066c918ca83acbaa", size = 151976, upload-time = "2026-05-10T18:16:51.062Z" }, + { url = "https://files.pythonhosted.org/packages/21/31/5072ad880946d83e5ea4147d6d018c78eefce85b77819b19bdd0ee229435/librt-0.11.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa0dd688aab3f7914d3e6e5e3554978e0383312fb8e771d84be008a35b9ee548", size = 557927, upload-time = "2026-05-10T18:16:52.632Z" }, + { url = "https://files.pythonhosted.org/packages/5e/8d/70b5fb7cfbab60edbe7381614ab985da58e144fbf465c86d44c95f43cdca/librt-0.11.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:f5fb36b8c6c63fdcbb1d526d94c0d1331610d43f4118cc1beb4efef4f3faacb2", size = 539698, upload-time = "2026-05-10T18:16:53.934Z" }, + { url = "https://files.pythonhosted.org/packages/fa/a3/ba3495a0b3edbd24a4cae0d1d3c64f39a9fc45d06e812101289b50c1a619/librt-0.11.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4a9a237d13addb93715b6fee74023d5ee3469b53fce527626c0e088aa585805f", size = 577162, upload-time = "2026-05-10T18:16:55.589Z" }, + { url = "https://files.pythonhosted.org/packages/f7/db/36e25fb81f99937ff1b96612a1dc9fd66f039cb9cc3aee12c01fac31aab9/librt-0.11.0-cp314-cp314t-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5ddd17bd87b2c56ddd60e546a7984a2e64c4e8eab92fb4cf3830a48ad5469d51", size = 566494, upload-time = "2026-05-10T18:16:56.975Z" }, + { url = "https://files.pythonhosted.org/packages/33/0d/3f622b47f0b013eeb9cf4cc07ae9bfe378d832a4eec998b2b209fe84244d/librt-0.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bd43992b4473d42f12ff9e68326079f0696d9d4e6000e8f39a0238d482ba6ee2", size = 596858, upload-time = "2026-05-10T18:16:58.374Z" }, + { url = "https://files.pythonhosted.org/packages/a9/02/71b90bc93039c46a2000651f6ad60122b114c8f54c4ad306e0e96f5b75ad/librt-0.11.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:f8e3e8056dd674e279741485e2e512d6e9a751c7455809d0114e6ebf8d781085", size = 590318, upload-time = "2026-05-10T18:16:59.676Z" }, + { url = "https://files.pythonhosted.org/packages/04/04/418cb3f75621e2b761fb1ab0f017f4d70a1a72a6e7c74ee4f7e8d198c2f3/librt-0.11.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:c1f708d8ae9c56cf38a903c44297243d2ec83fd82b396b977e0144a3e76217e3", size = 575115, upload-time = "2026-05-10T18:17:01.007Z" }, + { url = "https://files.pythonhosted.org/packages/cc/2c/5a2183ac58dd911f26b5d7e7d7d8f1d87fcecdddd99d6c12169a258ff62c/librt-0.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0add982e0e7b9fc14cf4b33789d5f13f66581889b88c2f58099f6ce8f92617bd", size = 617918, upload-time = "2026-05-10T18:17:02.682Z" }, + { url = "https://files.pythonhosted.org/packages/15/1f/dc6771a52592a4451be6effa200cbfc9cec61e4393d3033d81a9d307961d/librt-0.11.0-cp314-cp314t-win32.whl", hash = "sha256:2b481d846ac894c4e8403c5fd0e87c5d11d6499e404b474602508a224ff531c8", size = 103562, upload-time = "2026-05-10T18:17:03.99Z" }, + { url = "https://files.pythonhosted.org/packages/62/4a/7d1415567027286a75ba1093ec4aca11f073e0f559c530cf3e0a757ad55c/librt-0.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:28edb433edde181112a908c78907af28f964eabc15f4dd16c9d66c834302677c", size = 124327, upload-time = "2026-05-10T18:17:05.465Z" }, + { url = "https://files.pythonhosted.org/packages/ce/62/b40b382fa0c66fee1478073eb8db352a4a6beda4a1adccf1df911d8c289c/librt-0.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dee008f20b542e3cd162ba338a7f9ec0f6d23d395f66fe8aeeec3c9d067ea253", size = 102572, upload-time = "2026-05-10T18:17:06.809Z" }, ] [[package]] name = "locust" -version = "2.43.4" +version = "2.44.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "configargparse" }, @@ -1253,9 +1315,9 @@ dependencies = [ { name = "typing-extensions", marker = "python_full_version < '3.12'" }, { name = "werkzeug" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/52/be/6df1c778f673e1e2d785f262d20a4e130fdb8e51242466d7ae434b66a587/locust-2.43.4.tar.gz", hash = "sha256:4ace60f07f5fa9bf08d1b64da25915707befca19a790897eed6372656824deee", size = 1434321, upload-time = "2026-04-01T20:43:04.322Z" } +sdist = { url = "https://files.pythonhosted.org/packages/43/98/1501e70415157ec47c9020a682a0119c8ead46ae9f1aa79e007f779fda14/locust-2.44.1.tar.gz", hash = "sha256:962e6498431f152eca26d9cb158c23e3f61bf26e026f4f4171bed3f713379820", size = 1437151, upload-time = "2026-06-01T22:41:58.604Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c5/2c/a90d0b6fc476eb0f8e5a705f49e410563450b9b087688cc93a50eab54d63/locust-2.43.4-py3-none-any.whl", hash = "sha256:a4f40403e9f665e0dcb94991d9a8f19317d0d36afe88400833c5fab99ba942ed", size = 1454332, upload-time = "2026-04-01T20:43:02.767Z" }, + { url = "https://files.pythonhosted.org/packages/9f/f9/45b4c7849051deb080ba90c86bbe7d96969ba8858894eebcfdd921a173ac/locust-2.44.1-py3-none-any.whl", hash = "sha256:dba774c609fd56402ddf1ca07e3040ae7b436b4c2027393288e83167d24ac397", size = 1458058, upload-time = "2026-06-01T22:41:57.063Z" }, ] [[package]] @@ -1472,52 +1534,53 @@ wheels = [ [[package]] name = "mypy" -version = "1.20.1" +version = "2.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "ast-serialize" }, { name = "librt", marker = "platform_python_implementation != 'PyPy'" }, { name = "mypy-extensions" }, { name = "pathspec" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0b/3d/5b373635b3146264eb7a68d09e5ca11c305bbb058dfffbb47c47daf4f632/mypy-1.20.1.tar.gz", hash = "sha256:6fc3f4ecd52de81648fed1945498bf42fa2993ddfad67c9056df36ae5757f804", size = 3815892, upload-time = "2026-04-13T02:46:51.474Z" } +sdist = { url = "https://files.pythonhosted.org/packages/82/15/cca9d88503549ed6fedeaa1d448cdddd542ee8a490232d732e278036fbf2/mypy-2.1.0.tar.gz", hash = "sha256:81e76ad12c2d804512e9b13240d1588316531bfba07558286078bfbce9613633", size = 3898359, upload-time = "2026-05-11T18:37:36.237Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/82/0d/555ab7453cc4a4a8643b7f21c842b1a84c36b15392061ae7b052ee119320/mypy-1.20.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c01eb9bac2c6a962d00f9d23421cd2913840e65bba365167d057bd0b4171a92e", size = 14336012, upload-time = "2026-04-13T02:45:39.935Z" }, - { url = "https://files.pythonhosted.org/packages/57/26/85a28893f7db8a16ebb41d1e9dfcb4475844d06a88480b6639e32a74d6ef/mypy-1.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:55d12ddbd8a9cac5b276878bd534fa39fff5bf543dc6ae18f25d30c8d7d27fca", size = 13224636, upload-time = "2026-04-13T02:45:49.659Z" }, - { url = "https://files.pythonhosted.org/packages/93/41/bd4cd3c2caeb6c448b669222b8cfcbdee4a03b89431527b56fca9e56b6f3/mypy-1.20.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0aa322c1468b6cdfc927a44ce130f79bb44bcd34eb4a009eb9f96571fd80955", size = 13663471, upload-time = "2026-04-13T02:46:20.276Z" }, - { url = "https://files.pythonhosted.org/packages/3e/56/7ee8c471e10402d64b6517ae10434541baca053cffd81090e4097d5609d4/mypy-1.20.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3f8bc95899cf676b6e2285779a08a998cc3a7b26f1026752df9d2741df3c79e8", size = 14532344, upload-time = "2026-04-13T02:46:44.205Z" }, - { url = "https://files.pythonhosted.org/packages/b5/95/b37d1fa859a433f6156742e12f62b0bb75af658544fb6dada9363918743a/mypy-1.20.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:47c2b90191a870a04041e910277494b0d92f0711be9e524d45c074fe60c00b65", size = 14776670, upload-time = "2026-04-13T02:45:52.481Z" }, - { url = "https://files.pythonhosted.org/packages/03/77/b302e4cb0b80d2bdf6bf4fce5864bb4cbfa461f7099cea544eaf2457df78/mypy-1.20.1-cp311-cp311-win_amd64.whl", hash = "sha256:9857dc8d2ec1a392ffbda518075beb00ac58859979c79f9e6bdcb7277082c2f2", size = 10816524, upload-time = "2026-04-13T02:45:37.711Z" }, - { url = "https://files.pythonhosted.org/packages/7f/21/d969d7a68eb964993ebcc6170d5ecaf0cf65830c58ac3344562e16dc42a9/mypy-1.20.1-cp311-cp311-win_arm64.whl", hash = "sha256:09d8df92bb25b6065ab91b178da843dda67b33eb819321679a6e98a907ce0e10", size = 9750419, upload-time = "2026-04-13T02:45:08.542Z" }, - { url = "https://files.pythonhosted.org/packages/69/1b/75a7c825a02781ca10bc2f2f12fba2af5202f6d6005aad8d2d1f264d8d78/mypy-1.20.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:36ee2b9c6599c230fea89bbd79f401f9f9f8e9fcf0c777827789b19b7da90f51", size = 14494077, upload-time = "2026-04-13T02:45:55.085Z" }, - { url = "https://files.pythonhosted.org/packages/b0/54/5e5a569ea5c2b4d48b729fb32aa936eeb4246e4fc3e6f5b3d36a2dfbefb9/mypy-1.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fba3fb0968a7b48806b0c90f38d39296f10766885a94c83bd21399de1e14eb28", size = 13319495, upload-time = "2026-04-13T02:45:29.674Z" }, - { url = "https://files.pythonhosted.org/packages/6f/a4/a1945b19f33e91721b59deee3abb484f2fa5922adc33bb166daf5325d76d/mypy-1.20.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef1415a637cd3627d6304dfbeddbadd21079dafc2a8a753c477ce4fc0c2af54f", size = 13696948, upload-time = "2026-04-13T02:46:15.006Z" }, - { url = "https://files.pythonhosted.org/packages/b2/c6/75e969781c2359b2f9c15b061f28ec6d67c8b61865ceda176e85c8e7f2de/mypy-1.20.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ef3461b1ad5cd446e540016e90b5984657edda39f982f4cc45ca317b628f5a37", size = 14706744, upload-time = "2026-04-13T02:46:00.482Z" }, - { url = "https://files.pythonhosted.org/packages/a8/6e/b221b1de981fc4262fe3e0bf9ec272d292dfe42394a689c2d49765c144c4/mypy-1.20.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:542dd63c9e1339b6092eb25bd515f3a32a1453aee8c9521d2ddb17dacd840237", size = 14949035, upload-time = "2026-04-13T02:45:06.021Z" }, - { url = "https://files.pythonhosted.org/packages/ca/4b/298ba2de0aafc0da3ff2288da06884aae7ba6489bc247c933f87847c41b3/mypy-1.20.1-cp312-cp312-win_amd64.whl", hash = "sha256:1d55c7cd8ca22e31f93af2a01160a9e95465b5878de23dba7e48116052f20a8d", size = 10883216, upload-time = "2026-04-13T02:45:47.232Z" }, - { url = "https://files.pythonhosted.org/packages/c7/f9/5e25b8f0b8cb92f080bfed9c21d3279b2a0b6a601cdca369a039ba84789d/mypy-1.20.1-cp312-cp312-win_arm64.whl", hash = "sha256:f5b84a79070586e0d353ee07b719d9d0a4aa7c8ee90c0ea97747e98cbe193019", size = 9814299, upload-time = "2026-04-13T02:45:21.934Z" }, - { url = "https://files.pythonhosted.org/packages/21/e8/ef0991aa24c8f225df10b034f3c2681213cb54cf247623c6dec9a5744e70/mypy-1.20.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f3886c03e40afefd327bd70b3f634b39ea82e87f314edaa4d0cce4b927ddcc1", size = 14500739, upload-time = "2026-04-13T02:46:05.442Z" }, - { url = "https://files.pythonhosted.org/packages/23/73/416ebec3047636ed89fa871dc8c54bf05e9e20aa9499da59790d7adb312d/mypy-1.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e860eb3904f9764e83bafd70c8250bdffdc7dde6b82f486e8156348bf7ceb184", size = 13314735, upload-time = "2026-04-13T02:46:47.154Z" }, - { url = "https://files.pythonhosted.org/packages/10/1e/1505022d9c9ac2e014a384eb17638fb37bf8e9d0a833ea60605b66f8f7ba/mypy-1.20.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a4b5aac6e785719da51a84f5d09e9e843d473170a9045b1ea7ea1af86225df4b", size = 13704356, upload-time = "2026-04-13T02:45:19.773Z" }, - { url = "https://files.pythonhosted.org/packages/98/91/275b01f5eba5c467a3318ec214dd865abb66e9c811231c8587287b92876a/mypy-1.20.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f37b6cd0fe2ad3a20f05ace48ca3523fc52ff86940e34937b439613b6854472e", size = 14696420, upload-time = "2026-04-13T02:45:24.205Z" }, - { url = "https://files.pythonhosted.org/packages/a1/57/b3779e134e1b7250d05f874252780d0a88c068bc054bcff99ca20a3a2986/mypy-1.20.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e4bbb0f6b54ce7cc350ef4a770650d15fa70edd99ad5267e227133eda9c94218", size = 14936093, upload-time = "2026-04-13T02:45:32.087Z" }, - { url = "https://files.pythonhosted.org/packages/be/33/81b64991b0f3f278c3b55c335888794af190b2d59031a5ad1401bcb69f1e/mypy-1.20.1-cp313-cp313-win_amd64.whl", hash = "sha256:c3dc20f8ec76eecd77148cdd2f1542ed496e51e185713bf488a414f862deb8f2", size = 10889659, upload-time = "2026-04-13T02:46:02.926Z" }, - { url = "https://files.pythonhosted.org/packages/1b/fd/7adcb8053572edf5ef8f3db59599dfeeee3be9cc4c8c97e2d28f66f42ac5/mypy-1.20.1-cp313-cp313-win_arm64.whl", hash = "sha256:a9d62bbac5d6d46718e2b0330b25e6264463ed832722b8f7d4440ff1be3ca895", size = 9815515, upload-time = "2026-04-13T02:46:32.103Z" }, - { url = "https://files.pythonhosted.org/packages/40/cd/db831e84c81d57d4886d99feee14e372f64bbec6a9cb1a88a19e243f2ef5/mypy-1.20.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:12927b9c0ed794daedcf1dab055b6c613d9d5659ac511e8d936d96f19c087d12", size = 14483064, upload-time = "2026-04-13T02:45:26.901Z" }, - { url = "https://files.pythonhosted.org/packages/d5/82/74e62e7097fa67da328ac8ece8de09133448c04d20ddeaeba251a3000f01/mypy-1.20.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:752507dd481e958b2c08fc966d3806c962af5a9433b5bf8f3bdd7175c20e34fe", size = 13335694, upload-time = "2026-04-13T02:46:12.514Z" }, - { url = "https://files.pythonhosted.org/packages/74/c4/97e9a0abe4f3cdbbf4d079cb87a03b786efeccf5bf2b89fe4f96939ab2e6/mypy-1.20.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c614655b5a065e56274c6cbbe405f7cf7e96c0654db7ba39bc680238837f7b08", size = 13726365, upload-time = "2026-04-13T02:45:17.422Z" }, - { url = "https://files.pythonhosted.org/packages/d7/aa/a19d884a8d28fcd3c065776323029f204dbc774e70ec9c85eba228b680de/mypy-1.20.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2c3f6221a76f34d5100c6d35b3ef6b947054123c3f8d6938a4ba00b1308aa572", size = 14693472, upload-time = "2026-04-13T02:46:41.253Z" }, - { url = "https://files.pythonhosted.org/packages/84/44/cc9324bd21cf786592b44bf3b5d224b3923c1230ec9898d508d00241d465/mypy-1.20.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4bdfc06303ac06500af71ea0cdbe995c502b3c9ba32f3f8313523c137a25d1b6", size = 14919266, upload-time = "2026-04-13T02:46:28.37Z" }, - { url = "https://files.pythonhosted.org/packages/6e/dc/779abb25a8c63e8f44bf5a336217fa92790fa17e0c40e0c725d10cb01bbd/mypy-1.20.1-cp314-cp314-win_amd64.whl", hash = "sha256:0131edd7eba289973d1ba1003d1a37c426b85cdef76650cd02da6420898a5eb3", size = 11049713, upload-time = "2026-04-13T02:45:57.673Z" }, - { url = "https://files.pythonhosted.org/packages/28/08/4172be2ad7de9119b5a92ca36abbf641afdc5cb1ef4ae0c3a8182f29674f/mypy-1.20.1-cp314-cp314-win_arm64.whl", hash = "sha256:33f02904feb2c07e1fdf7909026206396c9deeb9e6f34d466b4cfedb0aadbbe4", size = 9999819, upload-time = "2026-04-13T02:46:35.039Z" }, - { url = "https://files.pythonhosted.org/packages/2d/af/af9e46b0c8eabbce9fc04a477564170f47a1c22b308822282a59b7ff315f/mypy-1.20.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:168472149dd8cc505c98cefd21ad77e4257ed6022cd5ed2fe2999bed56977a5a", size = 15547508, upload-time = "2026-04-13T02:46:25.588Z" }, - { url = "https://files.pythonhosted.org/packages/a7/cd/39c9e4ad6ba33e069e5837d772a9e6c304b4a5452a14a975d52b36444650/mypy-1.20.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:eb674600309a8f22790cca883a97c90299f948183ebb210fbef6bcee07cb1986", size = 14399557, upload-time = "2026-04-13T02:46:10.021Z" }, - { url = "https://files.pythonhosted.org/packages/83/c1/3fd71bdc118ffc502bf57559c909927bb7e011f327f7bb8e0488e98a5870/mypy-1.20.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef2b2e4cc464ba9795459f2586923abd58a0055487cbe558cb538ea6e6bc142a", size = 15045789, upload-time = "2026-04-13T02:45:10.81Z" }, - { url = "https://files.pythonhosted.org/packages/8e/73/6f07ff8b57a7d7b3e6e5bf34685d17632382395c8bb53364ec331661f83e/mypy-1.20.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dee461d396dd46b3f0ed5a098dbc9b8860c81c46ad44fa071afcfbc149f167c9", size = 15850795, upload-time = "2026-04-13T02:45:03.349Z" }, - { url = "https://files.pythonhosted.org/packages/ec/e2/f7dffec1c7767078f9e9adf0c786d1fe0ff30964a77eb213c09b8b58cb76/mypy-1.20.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e364926308b3e66f1361f81a566fc1b2f8cd47fc8525e8136d4058a65a4b4f02", size = 16088539, upload-time = "2026-04-13T02:46:17.841Z" }, - { url = "https://files.pythonhosted.org/packages/1a/76/e0dee71035316e75a69d73aec2f03c39c21c967b97e277fd0ef8fd6aec66/mypy-1.20.1-cp314-cp314t-win_amd64.whl", hash = "sha256:a0c17fbd746d38c70cbc42647cfd884f845a9708a4b160a8b4f7e70d41f4d7fa", size = 12575567, upload-time = "2026-04-13T02:45:34.795Z" }, - { url = "https://files.pythonhosted.org/packages/22/a8/7ed43c9d9c3d1468f86605e323a5d97e411a448790a00f07e779f3211a46/mypy-1.20.1-cp314-cp314t-win_arm64.whl", hash = "sha256:db2cb89654626a912efda69c0d5c1d22d948265e2069010d3dde3abf751c7d08", size = 10378823, upload-time = "2026-04-13T02:45:13.35Z" }, - { url = "https://files.pythonhosted.org/packages/d8/28/926bd972388e65a39ee98e188ccf67e81beb3aacfd5d6b310051772d974b/mypy-1.20.1-py3-none-any.whl", hash = "sha256:1aae28507f253fe82d883790d1c0a0d35798a810117c88184097fe8881052f06", size = 2636553, upload-time = "2026-04-13T02:46:30.45Z" }, + { url = "https://files.pythonhosted.org/packages/0a/a1/639f3024794a2a15899cb90707fe02e044c4412794c39c5769fd3df2e2ef/mypy-2.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a683016b16fe2f572dc04c72be7ee0504ac1605a265d0200f5cea695fb788f41", size = 14691685, upload-time = "2026-05-11T18:33:27.973Z" }, + { url = "https://files.pythonhosted.org/packages/3b/08/9a585dea4325f20d8b80dc78623fa50d1fd2173b710f6237afd6ba6ab39b/mypy-2.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1a293c534adb55271fef24a26da04b855540a8c13cc07bc5917b9fd2c394f2ca", size = 13555165, upload-time = "2026-05-11T18:32:16.107Z" }, + { url = "https://files.pythonhosted.org/packages/81/dc/7c42cc9c6cb01e8eb09961f1f738741d3e9c7e9d5c5b30ec69222625cd5f/mypy-2.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7406f4d048e71e576f5356d317e5b0a9e666dfd966bd99f9d14ca06e1a341538", size = 13994376, upload-time = "2026-05-11T18:32:39.256Z" }, + { url = "https://files.pythonhosted.org/packages/d4/fa/285946c33bce716e082c11dfeee9ee196eaf1f5042efb3581a31f9f205e4/mypy-2.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e0210d626fc8b31ccc90233754c7bc90e1f43205e85d96387f7db1285b55c398", size = 14864618, upload-time = "2026-05-11T18:34:49.765Z" }, + { url = "https://files.pythonhosted.org/packages/2b/83/82397f48af6c27e295d57979ded8490c9829040152cf7571b2f026aeb9a0/mypy-2.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3712c20deed54e814eaaa825603bada8ea1c390670a397c95b98405347acc563", size = 15102063, upload-time = "2026-05-11T18:34:05.855Z" }, + { url = "https://files.pythonhosted.org/packages/40/68/b02dec39057b88eb03dc0aa854732e26e8361f34f9d0e20c7614967d1eba/mypy-2.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fcaa0e479066e31f7cceb6a3bea39cb22b2ff51a6b2f24f193d19179ba17c389", size = 11060564, upload-time = "2026-05-11T18:35:36.494Z" }, + { url = "https://files.pythonhosted.org/packages/cf/a8/ea3dcbef31f99b634f2ee23bb0321cbc8c1b388b76a861eb849f13c347dc/mypy-2.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:0b1a5260c95aa443083f9ed3592662941951bca3d4ca224a5dc517c38b7cf666", size = 9966983, upload-time = "2026-05-11T18:37:14.139Z" }, + { url = "https://files.pythonhosted.org/packages/95/b1/55861beb5c339b44f9a2ba92df9e2cb1eeb4ae1eee674cdf7772c797778b/mypy-2.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:244358bf1c0da7722230bce60683d52e8e9fd030554926f15b747a84efb5b3af", size = 14874381, upload-time = "2026-05-11T18:37:31.784Z" }, + { url = "https://files.pythonhosted.org/packages/0b/b3/b7f770114b7d0ac92d0f76e8d93c2780844a70488a90e91821927850da86/mypy-2.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4ec7c57657493c7a75534df2751c8ae2cda383c16ecc55d2106c54476b1b16f6", size = 13665501, upload-time = "2026-05-11T18:34:23.063Z" }, + { url = "https://files.pythonhosted.org/packages/b6/f3/8ae2037967e2126689a0c11d99e2b707134a565191e92c60ca2572aec60a/mypy-2.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8161b6ff4392410023224f0969d17db93e1e154bc3e4ba62598e720723ae211", size = 14045750, upload-time = "2026-05-11T18:31:48.151Z" }, + { url = "https://files.pythonhosted.org/packages/a0/32/615eb5911859e43d054941b0d0a7d06cfa2870eba86529cf385b052b111c/mypy-2.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf03e12003084a67395184d3eb8cbd6a489dc3655b5664b28c210a9e2403ab0b", size = 15061630, upload-time = "2026-05-11T18:37:06.898Z" }, + { url = "https://files.pythonhosted.org/packages/d4/03/4eafbfff8bfab1b87082741eae6e6a624028c984e6708b73bce2a8570c9d/mypy-2.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:20509760fd791c51579d573153407d226385ec1f8bcce55d730b354f3336bc22", size = 15288831, upload-time = "2026-05-11T18:31:18.07Z" }, + { url = "https://files.pythonhosted.org/packages/99/ee/919661478e5891a3c96e549c036e467e64563ab85995b10c53c8358e16a3/mypy-2.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:6753d0c1fdd6b1a23b9e4f283ce80b2153b724adcb2653b20b85a8a28ac6436b", size = 11135228, upload-time = "2026-05-11T18:34:31.23Z" }, + { url = "https://files.pythonhosted.org/packages/24/0a/6a12b9782ca0831a553192f351679f4548abc9d19a7cc93bb7feb02084c7/mypy-2.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:98ebb6589bb3b6d0c6f0c459d53ca55b8091fbc13d277c4041c885392e8195e8", size = 10040684, upload-time = "2026-05-11T18:36:48.199Z" }, + { url = "https://files.pythonhosted.org/packages/6e/dd/c7191469c777f07689c032a8f7326e393ea34c92d6d76eb7ce5ba57ea66d/mypy-2.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:35aac3bb114e03888f535d5eb51b8bafbb3266586b599da1940f9b1be3ec5bd5", size = 14852174, upload-time = "2026-05-11T18:31:38.929Z" }, + { url = "https://files.pythonhosted.org/packages/55/8c/aed55408879043d72bb9135f4d0d19a02b886dd569631e113e3d2706cb8d/mypy-2.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8de55a8c861f2a49331f807be98d90caeceeef520bde13d43a160207f8af613e", size = 13651542, upload-time = "2026-05-11T18:36:04.636Z" }, + { url = "https://files.pythonhosted.org/packages/3a/8e/f371a824b1f1fa8ea6e3dbb8703d232977d572be2329554a3bc4d960302f/mypy-2.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5fdf2941a07434af755837d9880f7d7d25f1dacb1af9dcd4b9b66f2220a3024e", size = 14033929, upload-time = "2026-05-11T18:35:55.742Z" }, + { url = "https://files.pythonhosted.org/packages/94/21/f54be870d6dd53a82c674407e0f8eed7174b05ec78d42e5abd7b42e84fd5/mypy-2.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e195b817c13f02352a9c124301f9f30f078405444679b6753c1b96b6eed37285", size = 15039200, upload-time = "2026-05-11T18:33:10.281Z" }, + { url = "https://files.pythonhosted.org/packages/17/99/bf21748626a40ce59fd29a39386ab46afec88b7bd2f0fa6c3a97c995523f/mypy-2.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5431d42af987ebd92ba2f71d45c85ed41d8e6ca9f5fd209a69f68f707d2469e5", size = 15272690, upload-time = "2026-05-11T18:32:07.205Z" }, + { url = "https://files.pythonhosted.org/packages/d6/d7/9e90d2cf47100bea550ed2bc7b0d4de3a62181d84d5e37da0003e8462637/mypy-2.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:767fe8c66dc3e01e19e1737d4c38ebefead16125e1b8e58ad421903b376f5c65", size = 11147435, upload-time = "2026-05-11T18:33:56.477Z" }, + { url = "https://files.pythonhosted.org/packages/ec/46/e5c449e858798e35ffc90946282a27c62a77be743fe17480e4977374eb91/mypy-2.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:ecfe70d43775ab99562ab128ce49854a362044c9f894961f68f898c23cb7429d", size = 10035052, upload-time = "2026-05-11T18:32:30.049Z" }, + { url = "https://files.pythonhosted.org/packages/b0/ca/b279a672e874aedd5498ae25f722dacc8aa86bbffb939b3f97cbb1cf6686/mypy-2.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:7354c5a7f69d9345c3d6e69921d57088eea3ddeeb6b20d34c1b3855b02c36ec2", size = 14848422, upload-time = "2026-05-11T18:35:45.984Z" }, + { url = "https://files.pythonhosted.org/packages/27/e6/3efe56c631d959b9b4454e208b0ac4b7f4f58b404c89f8bec7b49efdfc21/mypy-2.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:49890d4f76ac9e06ec117f9e09f3174da70a620a0c300953d8595c926e80947f", size = 13677374, upload-time = "2026-05-11T18:36:57.188Z" }, + { url = "https://files.pythonhosted.org/packages/84/7f/8107ea87a44fd1f1b59882442f033c9c3488c127201b1d1d15f1cbd6022e/mypy-2.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:761be68e023ef5d94678772396a8af1220030f80837a3afd8d0aef3b419666f4", size = 14055743, upload-time = "2026-05-11T18:35:18.361Z" }, + { url = "https://files.pythonhosted.org/packages/51/4d/b6d34db183133b83761b9199a82d31557cdbb70a380d8c3b3438e11882a3/mypy-2.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c90345fc182dc363b891350457ec69c35140858538f38b4540845afcc32b1aef", size = 15020937, upload-time = "2026-05-11T18:34:59.618Z" }, + { url = "https://files.pythonhosted.org/packages/ff/d7/f08360c691d758acb02f45022c34d98b92892f4ea756644e1000d4b9f3d8/mypy-2.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b84802e7b5a6daf1f5e15bc9fcd7ddae77be13981ffab037f1c67bb84d67d135", size = 15253371, upload-time = "2026-05-11T18:36:41.081Z" }, + { url = "https://files.pythonhosted.org/packages/67/1b/09460a13719530a19bce27bd3bc8449e83569dd2ba7faf51c9c3c30c0b61/mypy-2.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:022c771234936ceac541ebaf836fe9e2abeb3f5e09aff21588fe543ff006fe21", size = 11326429, upload-time = "2026-05-11T18:34:13.526Z" }, + { url = "https://files.pythonhosted.org/packages/40/62/75dbf0f82f7b6680340efc614af29dd0b3c17b8a4f1cd09b8bd2fd6bc814/mypy-2.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:498207db725cec88829a6a5c2fc771205fd043719ef98bc49aba8fb9fc4e6d57", size = 10218799, upload-time = "2026-05-11T18:32:23.491Z" }, + { url = "https://files.pythonhosted.org/packages/b2/66/caca04ed7d972fb6eb6dd1ccd6df1de5c38fae8c5b3dc1c4e8e0d85ee6b9/mypy-2.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7d5e5cad0efeba72b93cd17490cc0d69c5ac9ca132994fe3fb0314808aeeb83e", size = 15923458, upload-time = "2026-05-11T18:35:28.64Z" }, + { url = "https://files.pythonhosted.org/packages/ed/52/2d90cbe49d014b13ed7ff337930c30bad35893fe38a1e4641e756bb62191/mypy-2.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ff715050c127d724fd260a2e666e7747fdd83511c0c47d449d98238970aef780", size = 14757697, upload-time = "2026-05-11T18:36:14.208Z" }, + { url = "https://files.pythonhosted.org/packages/ac/37/d98f4a14e081b238992d0ed96b6d39c7cc0148c9699eb71eaa68629665ea/mypy-2.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:82208da9e09414d520e912d3e462d454854bed0810b71540bb016dcbca7308fd", size = 15405638, upload-time = "2026-05-11T18:33:48.249Z" }, + { url = "https://files.pythonhosted.org/packages/a3/c2/15c46613b24a84fad2aea1248bf9619b99c2767ae9071fe224c179a0b7d4/mypy-2.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e79ebc1b904b84f0310dff7469655a9c36c7a68bddb37bdd42b67a332df61d08", size = 16215852, upload-time = "2026-05-11T18:32:50.296Z" }, + { url = "https://files.pythonhosted.org/packages/5c/90/9c16a57f482c76d25f6379762b56bbf65c711d8158cf271fb2802cfb0640/mypy-2.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e583edc957cfb0deb142079162ae826f58449b116c1d442f2d91c69d9fced081", size = 16452695, upload-time = "2026-05-11T18:33:38.182Z" }, + { url = "https://files.pythonhosted.org/packages/0f/4c/215a4eeb63cacc5f17f516691ea7285d11e249802b942476bff15922a314/mypy-2.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b33b6cd332695bba180d55e717a79d3038e479a2c49cc5eb3d53603409b9a5d7", size = 12866622, upload-time = "2026-05-11T18:34:39.945Z" }, + { url = "https://files.pythonhosted.org/packages/4b/50/1043e1db5f455ffe4c9ab22747cd8ca2bc492b1e4f4e21b130a44ee2b217/mypy-2.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:4f910fe825376a7b66ef7ca8c98e5a149e8cd64c19ae71d84047a74ee060d4e6", size = 10610798, upload-time = "2026-05-11T18:36:31.444Z" }, + { url = "https://files.pythonhosted.org/packages/0d/2a/13ca1f292f6db1b98ff495ef3467736b331621c5917cad984b7043e7348d/mypy-2.1.0-py3-none-any.whl", hash = "sha256:a663814603a5c563fb87a4f96fb473eeb30d1f5a4885afcf44f9db000a366289", size = 2693302, upload-time = "2026-05-11T18:31:29.246Z" }, ] [[package]] @@ -1540,83 +1603,81 @@ wheels = [ [[package]] name = "numpy" -version = "2.3.5" +version = "2.4.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/76/65/21b3bc86aac7b8f2862db1e808f1ea22b028e30a225a34a5ede9bf8678f2/numpy-2.3.5.tar.gz", hash = "sha256:784db1dcdab56bf0517743e746dfb0f885fc68d948aba86eeec2cba234bdf1c0", size = 20584950, upload-time = "2025-11-16T22:52:42.067Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/ad/fed0499ce6a338d2a03ebae59cd15093910c8875328855781952abf6c2fe/numpy-2.4.6.tar.gz", hash = "sha256:f3a3570c4a2a16746ac2c31a7c7c7b0c186b95ce902e33db6f28094ed7387dda", size = 20735807, upload-time = "2026-05-18T23:37:14.07Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/43/77/84dd1d2e34d7e2792a236ba180b5e8fcc1e3e414e761ce0253f63d7f572e/numpy-2.3.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:de5672f4a7b200c15a4127042170a694d4df43c992948f5e1af57f0174beed10", size = 17034641, upload-time = "2025-11-16T22:49:19.336Z" }, - { url = "https://files.pythonhosted.org/packages/2a/ea/25e26fa5837106cde46ae7d0b667e20f69cbbc0efd64cba8221411ab26ae/numpy-2.3.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:acfd89508504a19ed06ef963ad544ec6664518c863436306153e13e94605c218", size = 12528324, upload-time = "2025-11-16T22:49:22.582Z" }, - { url = "https://files.pythonhosted.org/packages/4d/1a/e85f0eea4cf03d6a0228f5c0256b53f2df4bc794706e7df019fc622e47f1/numpy-2.3.5-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:ffe22d2b05504f786c867c8395de703937f934272eb67586817b46188b4ded6d", size = 5356872, upload-time = "2025-11-16T22:49:25.408Z" }, - { url = "https://files.pythonhosted.org/packages/5c/bb/35ef04afd567f4c989c2060cde39211e4ac5357155c1833bcd1166055c61/numpy-2.3.5-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:872a5cf366aec6bb1147336480fef14c9164b154aeb6542327de4970282cd2f5", size = 6893148, upload-time = "2025-11-16T22:49:27.549Z" }, - { url = "https://files.pythonhosted.org/packages/f2/2b/05bbeb06e2dff5eab512dfc678b1cc5ee94d8ac5956a0885c64b6b26252b/numpy-2.3.5-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3095bdb8dd297e5920b010e96134ed91d852d81d490e787beca7e35ae1d89cf7", size = 14557282, upload-time = "2025-11-16T22:49:30.964Z" }, - { url = "https://files.pythonhosted.org/packages/65/fb/2b23769462b34398d9326081fad5655198fcf18966fcb1f1e49db44fbf31/numpy-2.3.5-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8cba086a43d54ca804ce711b2a940b16e452807acebe7852ff327f1ecd49b0d4", size = 16897903, upload-time = "2025-11-16T22:49:34.191Z" }, - { url = "https://files.pythonhosted.org/packages/ac/14/085f4cf05fc3f1e8aa95e85404e984ffca9b2275a5dc2b1aae18a67538b8/numpy-2.3.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6cf9b429b21df6b99f4dee7a1218b8b7ffbbe7df8764dc0bd60ce8a0708fed1e", size = 16341672, upload-time = "2025-11-16T22:49:37.2Z" }, - { url = "https://files.pythonhosted.org/packages/6f/3b/1f73994904142b2aa290449b3bb99772477b5fd94d787093e4f24f5af763/numpy-2.3.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:396084a36abdb603546b119d96528c2f6263921c50df3c8fd7cb28873a237748", size = 18838896, upload-time = "2025-11-16T22:49:39.727Z" }, - { url = "https://files.pythonhosted.org/packages/cd/b9/cf6649b2124f288309ffc353070792caf42ad69047dcc60da85ee85fea58/numpy-2.3.5-cp311-cp311-win32.whl", hash = "sha256:b0c7088a73aef3d687c4deef8452a3ac7c1be4e29ed8bf3b366c8111128ac60c", size = 6563608, upload-time = "2025-11-16T22:49:42.079Z" }, - { url = "https://files.pythonhosted.org/packages/aa/44/9fe81ae1dcc29c531843852e2874080dc441338574ccc4306b39e2ff6e59/numpy-2.3.5-cp311-cp311-win_amd64.whl", hash = "sha256:a414504bef8945eae5f2d7cb7be2d4af77c5d1cb5e20b296c2c25b61dff2900c", size = 13078442, upload-time = "2025-11-16T22:49:43.99Z" }, - { url = "https://files.pythonhosted.org/packages/6d/a7/f99a41553d2da82a20a2f22e93c94f928e4490bb447c9ff3c4ff230581d3/numpy-2.3.5-cp311-cp311-win_arm64.whl", hash = "sha256:0cd00b7b36e35398fa2d16af7b907b65304ef8bb4817a550e06e5012929830fa", size = 10458555, upload-time = "2025-11-16T22:49:47.092Z" }, - { url = "https://files.pythonhosted.org/packages/44/37/e669fe6cbb2b96c62f6bbedc6a81c0f3b7362f6a59230b23caa673a85721/numpy-2.3.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:74ae7b798248fe62021dbf3c914245ad45d1a6b0cb4a29ecb4b31d0bfbc4cc3e", size = 16733873, upload-time = "2025-11-16T22:49:49.84Z" }, - { url = "https://files.pythonhosted.org/packages/c5/65/df0db6c097892c9380851ab9e44b52d4f7ba576b833996e0080181c0c439/numpy-2.3.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ee3888d9ff7c14604052b2ca5535a30216aa0a58e948cdd3eeb8d3415f638769", size = 12259838, upload-time = "2025-11-16T22:49:52.863Z" }, - { url = "https://files.pythonhosted.org/packages/5b/e1/1ee06e70eb2136797abe847d386e7c0e830b67ad1d43f364dd04fa50d338/numpy-2.3.5-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:612a95a17655e213502f60cfb9bf9408efdc9eb1d5f50535cc6eb365d11b42b5", size = 5088378, upload-time = "2025-11-16T22:49:55.055Z" }, - { url = "https://files.pythonhosted.org/packages/6d/9c/1ca85fb86708724275103b81ec4cf1ac1d08f465368acfc8da7ab545bdae/numpy-2.3.5-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3101e5177d114a593d79dd79658650fe28b5a0d8abeb8ce6f437c0e6df5be1a4", size = 6628559, upload-time = "2025-11-16T22:49:57.371Z" }, - { url = "https://files.pythonhosted.org/packages/74/78/fcd41e5a0ce4f3f7b003da85825acddae6d7ecb60cf25194741b036ca7d6/numpy-2.3.5-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b973c57ff8e184109db042c842423ff4f60446239bd585a5131cc47f06f789d", size = 14250702, upload-time = "2025-11-16T22:49:59.632Z" }, - { url = "https://files.pythonhosted.org/packages/b6/23/2a1b231b8ff672b4c450dac27164a8b2ca7d9b7144f9c02d2396518352eb/numpy-2.3.5-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0d8163f43acde9a73c2a33605353a4f1bc4798745a8b1d73183b28e5b435ae28", size = 16606086, upload-time = "2025-11-16T22:50:02.127Z" }, - { url = "https://files.pythonhosted.org/packages/a0/c5/5ad26fbfbe2012e190cc7d5003e4d874b88bb18861d0829edc140a713021/numpy-2.3.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:51c1e14eb1e154ebd80e860722f9e6ed6ec89714ad2db2d3aa33c31d7c12179b", size = 16025985, upload-time = "2025-11-16T22:50:04.536Z" }, - { url = "https://files.pythonhosted.org/packages/d2/fa/dd48e225c46c819288148d9d060b047fd2a6fb1eb37eae25112ee4cb4453/numpy-2.3.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b46b4ec24f7293f23adcd2d146960559aaf8020213de8ad1909dba6c013bf89c", size = 18542976, upload-time = "2025-11-16T22:50:07.557Z" }, - { url = "https://files.pythonhosted.org/packages/05/79/ccbd23a75862d95af03d28b5c6901a1b7da4803181513d52f3b86ed9446e/numpy-2.3.5-cp312-cp312-win32.whl", hash = "sha256:3997b5b3c9a771e157f9aae01dd579ee35ad7109be18db0e85dbdbe1de06e952", size = 6285274, upload-time = "2025-11-16T22:50:10.746Z" }, - { url = "https://files.pythonhosted.org/packages/2d/57/8aeaf160312f7f489dea47ab61e430b5cb051f59a98ae68b7133ce8fa06a/numpy-2.3.5-cp312-cp312-win_amd64.whl", hash = "sha256:86945f2ee6d10cdfd67bcb4069c1662dd711f7e2a4343db5cecec06b87cf31aa", size = 12782922, upload-time = "2025-11-16T22:50:12.811Z" }, - { url = "https://files.pythonhosted.org/packages/78/a6/aae5cc2ca78c45e64b9ef22f089141d661516856cf7c8a54ba434576900d/numpy-2.3.5-cp312-cp312-win_arm64.whl", hash = "sha256:f28620fe26bee16243be2b7b874da327312240a7cdc38b769a697578d2100013", size = 10194667, upload-time = "2025-11-16T22:50:16.16Z" }, - { url = "https://files.pythonhosted.org/packages/db/69/9cde09f36da4b5a505341180a3f2e6fadc352fd4d2b7096ce9778db83f1a/numpy-2.3.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d0f23b44f57077c1ede8c5f26b30f706498b4862d3ff0a7298b8411dd2f043ff", size = 16728251, upload-time = "2025-11-16T22:50:19.013Z" }, - { url = "https://files.pythonhosted.org/packages/79/fb/f505c95ceddd7027347b067689db71ca80bd5ecc926f913f1a23e65cf09b/numpy-2.3.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:aa5bc7c5d59d831d9773d1170acac7893ce3a5e130540605770ade83280e7188", size = 12254652, upload-time = "2025-11-16T22:50:21.487Z" }, - { url = "https://files.pythonhosted.org/packages/78/da/8c7738060ca9c31b30e9301ee0cf6c5ffdbf889d9593285a1cead337f9a5/numpy-2.3.5-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:ccc933afd4d20aad3c00bcef049cb40049f7f196e0397f1109dba6fed63267b0", size = 5083172, upload-time = "2025-11-16T22:50:24.562Z" }, - { url = "https://files.pythonhosted.org/packages/a4/b4/ee5bb2537fb9430fd2ef30a616c3672b991a4129bb1c7dcc42aa0abbe5d7/numpy-2.3.5-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:afaffc4393205524af9dfa400fa250143a6c3bc646c08c9f5e25a9f4b4d6a903", size = 6622990, upload-time = "2025-11-16T22:50:26.47Z" }, - { url = "https://files.pythonhosted.org/packages/95/03/dc0723a013c7d7c19de5ef29e932c3081df1c14ba582b8b86b5de9db7f0f/numpy-2.3.5-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c75442b2209b8470d6d5d8b1c25714270686f14c749028d2199c54e29f20b4d", size = 14248902, upload-time = "2025-11-16T22:50:28.861Z" }, - { url = "https://files.pythonhosted.org/packages/f5/10/ca162f45a102738958dcec8023062dad0cbc17d1ab99d68c4e4a6c45fb2b/numpy-2.3.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11e06aa0af8c0f05104d56450d6093ee639e15f24ecf62d417329d06e522e017", size = 16597430, upload-time = "2025-11-16T22:50:31.56Z" }, - { url = "https://files.pythonhosted.org/packages/2a/51/c1e29be863588db58175175f057286900b4b3327a1351e706d5e0f8dd679/numpy-2.3.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ed89927b86296067b4f81f108a2271d8926467a8868e554eaf370fc27fa3ccaf", size = 16024551, upload-time = "2025-11-16T22:50:34.242Z" }, - { url = "https://files.pythonhosted.org/packages/83/68/8236589d4dbb87253d28259d04d9b814ec0ecce7cb1c7fed29729f4c3a78/numpy-2.3.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51c55fe3451421f3a6ef9a9c1439e82101c57a2c9eab9feb196a62b1a10b58ce", size = 18533275, upload-time = "2025-11-16T22:50:37.651Z" }, - { url = "https://files.pythonhosted.org/packages/40/56/2932d75b6f13465239e3b7b7e511be27f1b8161ca2510854f0b6e521c395/numpy-2.3.5-cp313-cp313-win32.whl", hash = "sha256:1978155dd49972084bd6ef388d66ab70f0c323ddee6f693d539376498720fb7e", size = 6277637, upload-time = "2025-11-16T22:50:40.11Z" }, - { url = "https://files.pythonhosted.org/packages/0c/88/e2eaa6cffb115b85ed7c7c87775cb8bcf0816816bc98ca8dbfa2ee33fe6e/numpy-2.3.5-cp313-cp313-win_amd64.whl", hash = "sha256:00dc4e846108a382c5869e77c6ed514394bdeb3403461d25a829711041217d5b", size = 12779090, upload-time = "2025-11-16T22:50:42.503Z" }, - { url = "https://files.pythonhosted.org/packages/8f/88/3f41e13a44ebd4034ee17baa384acac29ba6a4fcc2aca95f6f08ca0447d1/numpy-2.3.5-cp313-cp313-win_arm64.whl", hash = "sha256:0472f11f6ec23a74a906a00b48a4dcf3849209696dff7c189714511268d103ae", size = 10194710, upload-time = "2025-11-16T22:50:44.971Z" }, - { url = "https://files.pythonhosted.org/packages/13/cb/71744144e13389d577f867f745b7df2d8489463654a918eea2eeb166dfc9/numpy-2.3.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:414802f3b97f3c1eef41e530aaba3b3c1620649871d8cb38c6eaff034c2e16bd", size = 16827292, upload-time = "2025-11-16T22:50:47.715Z" }, - { url = "https://files.pythonhosted.org/packages/71/80/ba9dc6f2a4398e7f42b708a7fdc841bb638d353be255655498edbf9a15a8/numpy-2.3.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5ee6609ac3604fa7780e30a03e5e241a7956f8e2fcfe547d51e3afa5247ac47f", size = 12378897, upload-time = "2025-11-16T22:50:51.327Z" }, - { url = "https://files.pythonhosted.org/packages/2e/6d/db2151b9f64264bcceccd51741aa39b50150de9b602d98ecfe7e0c4bff39/numpy-2.3.5-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:86d835afea1eaa143012a2d7a3f45a3adce2d7adc8b4961f0b362214d800846a", size = 5207391, upload-time = "2025-11-16T22:50:54.542Z" }, - { url = "https://files.pythonhosted.org/packages/80/ae/429bacace5ccad48a14c4ae5332f6aa8ab9f69524193511d60ccdfdc65fa/numpy-2.3.5-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:30bc11310e8153ca664b14c5f1b73e94bd0503681fcf136a163de856f3a50139", size = 6721275, upload-time = "2025-11-16T22:50:56.794Z" }, - { url = "https://files.pythonhosted.org/packages/74/5b/1919abf32d8722646a38cd527bc3771eb229a32724ee6ba340ead9b92249/numpy-2.3.5-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1062fde1dcf469571705945b0f221b73928f34a20c904ffb45db101907c3454e", size = 14306855, upload-time = "2025-11-16T22:50:59.208Z" }, - { url = "https://files.pythonhosted.org/packages/a5/87/6831980559434973bebc30cd9c1f21e541a0f2b0c280d43d3afd909b66d0/numpy-2.3.5-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce581db493ea1a96c0556360ede6607496e8bf9b3a8efa66e06477267bc831e9", size = 16657359, upload-time = "2025-11-16T22:51:01.991Z" }, - { url = "https://files.pythonhosted.org/packages/dd/91/c797f544491ee99fd00495f12ebb7802c440c1915811d72ac5b4479a3356/numpy-2.3.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:cc8920d2ec5fa99875b670bb86ddeb21e295cb07aa331810d9e486e0b969d946", size = 16093374, upload-time = "2025-11-16T22:51:05.291Z" }, - { url = "https://files.pythonhosted.org/packages/74/a6/54da03253afcbe7a72785ec4da9c69fb7a17710141ff9ac5fcb2e32dbe64/numpy-2.3.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:9ee2197ef8c4f0dfe405d835f3b6a14f5fee7782b5de51ba06fb65fc9b36e9f1", size = 18594587, upload-time = "2025-11-16T22:51:08.585Z" }, - { url = "https://files.pythonhosted.org/packages/80/e9/aff53abbdd41b0ecca94285f325aff42357c6b5abc482a3fcb4994290b18/numpy-2.3.5-cp313-cp313t-win32.whl", hash = "sha256:70b37199913c1bd300ff6e2693316c6f869c7ee16378faf10e4f5e3275b299c3", size = 6405940, upload-time = "2025-11-16T22:51:11.541Z" }, - { url = "https://files.pythonhosted.org/packages/d5/81/50613fec9d4de5480de18d4f8ef59ad7e344d497edbef3cfd80f24f98461/numpy-2.3.5-cp313-cp313t-win_amd64.whl", hash = "sha256:b501b5fa195cc9e24fe102f21ec0a44dffc231d2af79950b451e0d99cea02234", size = 12920341, upload-time = "2025-11-16T22:51:14.312Z" }, - { url = "https://files.pythonhosted.org/packages/bb/ab/08fd63b9a74303947f34f0bd7c5903b9c5532c2d287bead5bdf4c556c486/numpy-2.3.5-cp313-cp313t-win_arm64.whl", hash = "sha256:a80afd79f45f3c4a7d341f13acbe058d1ca8ac017c165d3fa0d3de6bc1a079d7", size = 10262507, upload-time = "2025-11-16T22:51:16.846Z" }, - { url = "https://files.pythonhosted.org/packages/ba/97/1a914559c19e32d6b2e233cf9a6a114e67c856d35b1d6babca571a3e880f/numpy-2.3.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:bf06bc2af43fa8d32d30fae16ad965663e966b1a3202ed407b84c989c3221e82", size = 16735706, upload-time = "2025-11-16T22:51:19.558Z" }, - { url = "https://files.pythonhosted.org/packages/57/d4/51233b1c1b13ecd796311216ae417796b88b0616cfd8a33ae4536330748a/numpy-2.3.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:052e8c42e0c49d2575621c158934920524f6c5da05a1d3b9bab5d8e259e045f0", size = 12264507, upload-time = "2025-11-16T22:51:22.492Z" }, - { url = "https://files.pythonhosted.org/packages/45/98/2fe46c5c2675b8306d0b4a3ec3494273e93e1226a490f766e84298576956/numpy-2.3.5-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:1ed1ec893cff7040a02c8aa1c8611b94d395590d553f6b53629a4461dc7f7b63", size = 5093049, upload-time = "2025-11-16T22:51:25.171Z" }, - { url = "https://files.pythonhosted.org/packages/ce/0e/0698378989bb0ac5f1660c81c78ab1fe5476c1a521ca9ee9d0710ce54099/numpy-2.3.5-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:2dcd0808a421a482a080f89859a18beb0b3d1e905b81e617a188bd80422d62e9", size = 6626603, upload-time = "2025-11-16T22:51:27Z" }, - { url = "https://files.pythonhosted.org/packages/5e/a6/9ca0eecc489640615642a6cbc0ca9e10df70df38c4d43f5a928ff18d8827/numpy-2.3.5-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:727fd05b57df37dc0bcf1a27767a3d9a78cbbc92822445f32cc3436ba797337b", size = 14262696, upload-time = "2025-11-16T22:51:29.402Z" }, - { url = "https://files.pythonhosted.org/packages/c8/f6/07ec185b90ec9d7217a00eeeed7383b73d7e709dae2a9a021b051542a708/numpy-2.3.5-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fffe29a1ef00883599d1dc2c51aa2e5d80afe49523c261a74933df395c15c520", size = 16597350, upload-time = "2025-11-16T22:51:32.167Z" }, - { url = "https://files.pythonhosted.org/packages/75/37/164071d1dde6a1a84c9b8e5b414fa127981bad47adf3a6b7e23917e52190/numpy-2.3.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8f7f0e05112916223d3f438f293abf0727e1181b5983f413dfa2fefc4098245c", size = 16040190, upload-time = "2025-11-16T22:51:35.403Z" }, - { url = "https://files.pythonhosted.org/packages/08/3c/f18b82a406b04859eb026d204e4e1773eb41c5be58410f41ffa511d114ae/numpy-2.3.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2e2eb32ddb9ccb817d620ac1d8dae7c3f641c1e5f55f531a33e8ab97960a75b8", size = 18536749, upload-time = "2025-11-16T22:51:39.698Z" }, - { url = "https://files.pythonhosted.org/packages/40/79/f82f572bf44cf0023a2fe8588768e23e1592585020d638999f15158609e1/numpy-2.3.5-cp314-cp314-win32.whl", hash = "sha256:66f85ce62c70b843bab1fb14a05d5737741e74e28c7b8b5a064de10142fad248", size = 6335432, upload-time = "2025-11-16T22:51:42.476Z" }, - { url = "https://files.pythonhosted.org/packages/a3/2e/235b4d96619931192c91660805e5e49242389742a7a82c27665021db690c/numpy-2.3.5-cp314-cp314-win_amd64.whl", hash = "sha256:e6a0bc88393d65807d751a614207b7129a310ca4fe76a74e5c7da5fa5671417e", size = 12919388, upload-time = "2025-11-16T22:51:45.275Z" }, - { url = "https://files.pythonhosted.org/packages/07/2b/29fd75ce45d22a39c61aad74f3d718e7ab67ccf839ca8b60866054eb15f8/numpy-2.3.5-cp314-cp314-win_arm64.whl", hash = "sha256:aeffcab3d4b43712bb7a60b65f6044d444e75e563ff6180af8f98dd4b905dfd2", size = 10476651, upload-time = "2025-11-16T22:51:47.749Z" }, - { url = "https://files.pythonhosted.org/packages/17/e1/f6a721234ebd4d87084cfa68d081bcba2f5cfe1974f7de4e0e8b9b2a2ba1/numpy-2.3.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:17531366a2e3a9e30762c000f2c43a9aaa05728712e25c11ce1dbe700c53ad41", size = 16834503, upload-time = "2025-11-16T22:51:50.443Z" }, - { url = "https://files.pythonhosted.org/packages/5c/1c/baf7ffdc3af9c356e1c135e57ab7cf8d247931b9554f55c467efe2c69eff/numpy-2.3.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d21644de1b609825ede2f48be98dfde4656aefc713654eeee280e37cadc4e0ad", size = 12381612, upload-time = "2025-11-16T22:51:53.609Z" }, - { url = "https://files.pythonhosted.org/packages/74/91/f7f0295151407ddc9ba34e699013c32c3c91944f9b35fcf9281163dc1468/numpy-2.3.5-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:c804e3a5aba5460c73955c955bdbd5c08c354954e9270a2c1565f62e866bdc39", size = 5210042, upload-time = "2025-11-16T22:51:56.213Z" }, - { url = "https://files.pythonhosted.org/packages/2e/3b/78aebf345104ec50dd50a4d06ddeb46a9ff5261c33bcc58b1c4f12f85ec2/numpy-2.3.5-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:cc0a57f895b96ec78969c34f682c602bf8da1a0270b09bc65673df2e7638ec20", size = 6724502, upload-time = "2025-11-16T22:51:58.584Z" }, - { url = "https://files.pythonhosted.org/packages/02/c6/7c34b528740512e57ef1b7c8337ab0b4f0bddf34c723b8996c675bc2bc91/numpy-2.3.5-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:900218e456384ea676e24ea6a0417f030a3b07306d29d7ad843957b40a9d8d52", size = 14308962, upload-time = "2025-11-16T22:52:01.698Z" }, - { url = "https://files.pythonhosted.org/packages/80/35/09d433c5262bc32d725bafc619e095b6a6651caf94027a03da624146f655/numpy-2.3.5-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:09a1bea522b25109bf8e6f3027bd810f7c1085c64a0c7ce050c1676ad0ba010b", size = 16655054, upload-time = "2025-11-16T22:52:04.267Z" }, - { url = "https://files.pythonhosted.org/packages/7a/ab/6a7b259703c09a88804fa2430b43d6457b692378f6b74b356155283566ac/numpy-2.3.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:04822c00b5fd0323c8166d66c701dc31b7fbd252c100acd708c48f763968d6a3", size = 16091613, upload-time = "2025-11-16T22:52:08.651Z" }, - { url = "https://files.pythonhosted.org/packages/c2/88/330da2071e8771e60d1038166ff9d73f29da37b01ec3eb43cb1427464e10/numpy-2.3.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d6889ec4ec662a1a37eb4b4fb26b6100841804dac55bd9df579e326cdc146227", size = 18591147, upload-time = "2025-11-16T22:52:11.453Z" }, - { url = "https://files.pythonhosted.org/packages/51/41/851c4b4082402d9ea860c3626db5d5df47164a712cb23b54be028b184c1c/numpy-2.3.5-cp314-cp314t-win32.whl", hash = "sha256:93eebbcf1aafdf7e2ddd44c2923e2672e1010bddc014138b229e49725b4d6be5", size = 6479806, upload-time = "2025-11-16T22:52:14.641Z" }, - { url = "https://files.pythonhosted.org/packages/90/30/d48bde1dfd93332fa557cff1972fbc039e055a52021fbef4c2c4b1eefd17/numpy-2.3.5-cp314-cp314t-win_amd64.whl", hash = "sha256:c8a9958e88b65c3b27e22ca2a076311636850b612d6bbfb76e8d156aacde2aaf", size = 13105760, upload-time = "2025-11-16T22:52:17.975Z" }, - { url = "https://files.pythonhosted.org/packages/2d/fd/4b5eb0b3e888d86aee4d198c23acec7d214baaf17ea93c1adec94c9518b9/numpy-2.3.5-cp314-cp314t-win_arm64.whl", hash = "sha256:6203fdf9f3dc5bdaed7319ad8698e685c7a3be10819f41d32a0723e611733b42", size = 10545459, upload-time = "2025-11-16T22:52:20.55Z" }, - { url = "https://files.pythonhosted.org/packages/c6/65/f9dea8e109371ade9c782b4e4756a82edf9d3366bca495d84d79859a0b79/numpy-2.3.5-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:f0963b55cdd70fad460fa4c1341f12f976bb26cb66021a5580329bd498988310", size = 16910689, upload-time = "2025-11-16T22:52:23.247Z" }, - { url = "https://files.pythonhosted.org/packages/00/4f/edb00032a8fb92ec0a679d3830368355da91a69cab6f3e9c21b64d0bb986/numpy-2.3.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:f4255143f5160d0de972d28c8f9665d882b5f61309d8362fdd3e103cf7bf010c", size = 12457053, upload-time = "2025-11-16T22:52:26.367Z" }, - { url = "https://files.pythonhosted.org/packages/16/a4/e8a53b5abd500a63836a29ebe145fc1ab1f2eefe1cfe59276020373ae0aa/numpy-2.3.5-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:a4b9159734b326535f4dd01d947f919c6eefd2d9827466a696c44ced82dfbc18", size = 5285635, upload-time = "2025-11-16T22:52:29.266Z" }, - { url = "https://files.pythonhosted.org/packages/a3/2f/37eeb9014d9c8b3e9c55bc599c68263ca44fdbc12a93e45a21d1d56df737/numpy-2.3.5-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:2feae0d2c91d46e59fcd62784a3a83b3fb677fead592ce51b5a6fbb4f95965ff", size = 6801770, upload-time = "2025-11-16T22:52:31.421Z" }, - { url = "https://files.pythonhosted.org/packages/7d/e4/68d2f474df2cb671b2b6c2986a02e520671295647dad82484cde80ca427b/numpy-2.3.5-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ffac52f28a7849ad7576293c0cb7b9f08304e8f7d738a8cb8a90ec4c55a998eb", size = 14391768, upload-time = "2025-11-16T22:52:33.593Z" }, - { url = "https://files.pythonhosted.org/packages/b8/50/94ccd8a2b141cb50651fddd4f6a48874acb3c91c8f0842b08a6afc4b0b21/numpy-2.3.5-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63c0e9e7eea69588479ebf4a8a270d5ac22763cc5854e9a7eae952a3908103f7", size = 16729263, upload-time = "2025-11-16T22:52:36.369Z" }, - { url = "https://files.pythonhosted.org/packages/2d/ee/346fa473e666fe14c52fcdd19ec2424157290a032d4c41f98127bfb31ac7/numpy-2.3.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f16417ec91f12f814b10bafe79ef77e70113a2f5f7018640e7425ff979253425", size = 12967213, upload-time = "2025-11-16T22:52:39.38Z" }, + { url = "https://files.pythonhosted.org/packages/b3/49/ec46835a70be8fa6446c495126ac84fdb28cb2558e1620ffb87a10c8b64c/numpy-2.4.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0280e0356c0829a18d9de1cb7eee50ec22ca639878d7240307ca0943d73cd2c4", size = 16969194, upload-time = "2026-05-18T23:33:13.503Z" }, + { url = "https://files.pythonhosted.org/packages/0e/0d/f5957185c0ee2f3e12f78715aa9e3b353fd83633316c8532b38faa37e3f6/numpy-2.4.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:110f8b71aacb688ec69062bb7f6938a0f8acb01b7c1c4beb453c65b6d234584d", size = 14964111, upload-time = "2026-05-18T23:33:17.795Z" }, + { url = "https://files.pythonhosted.org/packages/ad/40/40a40ee0ddf7ceb782c49af278894b686e586d65d8c1889c8b5da01a3d7d/numpy-2.4.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:4cfe66903cc32a9921a6733d96b19bb6abf310397581bbad89c228f5abaf0ee8", size = 5469159, upload-time = "2026-05-18T23:33:20.654Z" }, + { url = "https://files.pythonhosted.org/packages/63/13/f9a8046535cb21deae82f8d03de9617e08882d274fad2539630761888228/numpy-2.4.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8155154c7c691289fe18f510b5d4657c68c67989f293f0535a91360392ff6538", size = 6798936, upload-time = "2026-05-18T23:33:22.987Z" }, + { url = "https://files.pythonhosted.org/packages/33/a8/6fa8c1a345a8c85dbb21932c447bee07c30a2c2a3f31e369c0a84b300147/numpy-2.4.6-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ab0a9c4ffb1a6d95ef519fe4247dba8eb6b18ad93999f76b7f657039acabd47", size = 15966692, upload-time = "2026-05-18T23:33:26.62Z" }, + { url = "https://files.pythonhosted.org/packages/02/03/74fe2a4cb3817d94d86402f2506554130a2f01414e299b5a843e5a8a957f/numpy-2.4.6-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:89cd468399cfd2504718f0ba50e410dca55a170b61a02ad92bb18c8a65186e93", size = 16918164, upload-time = "2026-05-18T23:33:29.955Z" }, + { url = "https://files.pythonhosted.org/packages/c5/80/3615be3313f7e7696609bc194b9f0101da809df79e859bdb84e0cd043f46/numpy-2.4.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c2d37ab77531417474168eb79d6d80b14f821a966818505d03013d0833edb7a8", size = 17322877, upload-time = "2026-05-18T23:33:34.724Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ac/a691e0fe2675e370d0e08ff905adc49a1c8830e8cae03efe4477e92cd55d/numpy-2.4.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f407cb6b8e9d6d8c626bc73c945db1706035af8fd632295547bf1c9e46d092d6", size = 18651487, upload-time = "2026-05-18T23:33:38.217Z" }, + { url = "https://files.pythonhosted.org/packages/15/a7/9bc1cd626d7bf6869bfedf27b91b6ab5dd607758bf8e959d6fa80c6a59cb/numpy-2.4.6-cp311-cp311-win32.whl", hash = "sha256:ddea102b48f9e339f3948bf22040944184627a30fdf7f858667673b9c5f033c8", size = 6233945, upload-time = "2026-05-18T23:33:41.331Z" }, + { url = "https://files.pythonhosted.org/packages/c5/31/7fc6239c12bce7e931463251cca4426c465e1876ba3cc785402ef4dd8f4e/numpy-2.4.6-cp311-cp311-win_amd64.whl", hash = "sha256:1e254a00cdf42b1e4d5b3d68d33af63268d41340d8885df2ab6470f2e1500147", size = 12608406, upload-time = "2026-05-18T23:33:44.131Z" }, + { url = "https://files.pythonhosted.org/packages/27/83/140f85a466595a16382996a1bf06b2b54bcd597488921b0c9daaeeda72af/numpy-2.4.6-cp311-cp311-win_arm64.whl", hash = "sha256:ed9749eef4cbd126da3dc1d6bcb3a57f5eb7ac6a6484146bdbf743f552dfc577", size = 10479528, upload-time = "2026-05-18T23:33:50.725Z" }, + { url = "https://files.pythonhosted.org/packages/95/2a/3d7b5ac8aac24feaf9ad7ed58f45b0bbc06d37e4338ae84c9f2298b570f9/numpy-2.4.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:001fbb8e08d942dd57599e781f2472269ee7f2755fae407b4f67b2f0b17da3f1", size = 16689119, upload-time = "2026-05-18T23:33:54.065Z" }, + { url = "https://files.pythonhosted.org/packages/ea/12/92c4c131527599e8288d6918e888d88726f84d805d784b771f32408aeaef/numpy-2.4.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ebfb099f8dcf083deef3ac1ca4c1503f387cf76296fcb3816b66f5ecb5f54fdb", size = 14699246, upload-time = "2026-05-18T23:33:57.621Z" }, + { url = "https://files.pythonhosted.org/packages/ad/fe/c0a6b7b2ca128a8fb228575147073b660656734b8ebe4d76c8fd748dcc79/numpy-2.4.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:3213d622a0283a39a93d188f3cf72b26862df52fbb4ca3697f51705016523d41", size = 5204410, upload-time = "2026-05-18T23:34:00.302Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d4/9770d14ba719432bb90a421bfd443872ed0f70f7264b64bec12ea363d5fd/numpy-2.4.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:357cc07a6d7b0b182ff02249616a03742827ebb1277546b5c7cd7f7620a45698", size = 6551240, upload-time = "2026-05-18T23:34:02.852Z" }, + { url = "https://files.pythonhosted.org/packages/c9/c6/50a46a6205feba2343f1d6d17438107c5dc491ed1c736e6ea68689fd906b/numpy-2.4.6-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f9fb9157b4ce2971008323afe46053787b526ef624fea915b261468a8421a0f", size = 15671012, upload-time = "2026-05-18T23:34:05.485Z" }, + { url = "https://files.pythonhosted.org/packages/99/60/14115e6364fa676c5397c2ad3004e527e9aa487abf5d0706ec81bbd08529/numpy-2.4.6-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90f9849678c75fe7afa2d348ac842c168b0a4d3d61919687216dfc547976d853", size = 16645538, upload-time = "2026-05-18T23:34:09.265Z" }, + { url = "https://files.pythonhosted.org/packages/ae/c5/693cbe59e57db94d2231fa519ca3978dc9e19da5a8f088588f5c6e947ff2/numpy-2.4.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c1a2af6c6ef86344a6b0db6b97834208bf598db514f2b155042439b62605601a", size = 17020706, upload-time = "2026-05-18T23:34:13.053Z" }, + { url = "https://files.pythonhosted.org/packages/ef/fc/85b7c4eff9b4966ade25c2273cf7e7012e92366c032058653934b37de044/numpy-2.4.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e5805d5a22fd19c8ccff10a9561f9df94436b0545619ea579db2d3c35294bce2", size = 18368541, upload-time = "2026-05-18T23:34:17.024Z" }, + { url = "https://files.pythonhosted.org/packages/f6/81/e1b27545deedce7f4a0b348618c6b62d74e36a4dc9ccd42f3eb2f85eee32/numpy-2.4.6-cp312-cp312-win32.whl", hash = "sha256:e3eeb0aabd6bd5ce64faae67e9935203a6991b4bc2a485a767fbafb2c5125f45", size = 5962825, upload-time = "2026-05-18T23:34:20.3Z" }, + { url = "https://files.pythonhosted.org/packages/ab/ca/feab00bd44aa5fe1ad2c18f08b4d3bb92e26484b0b1d1443897809ed528c/numpy-2.4.6-cp312-cp312-win_amd64.whl", hash = "sha256:d8e8286dd7cea7895157318d1b91cdacac64c479f3cbc8dce548331728484751", size = 12321687, upload-time = "2026-05-18T23:34:23.095Z" }, + { url = "https://files.pythonhosted.org/packages/63/cf/5a6d34850a39d1093558564f77ee8e8e0bee5061151b8f05a55711001ec7/numpy-2.4.6-cp312-cp312-win_arm64.whl", hash = "sha256:4081eb135ac24158bd51cdfbef16f1c64df7063b1143f24731387137c092bec8", size = 10221482, upload-time = "2026-05-18T23:34:25.876Z" }, + { url = "https://files.pythonhosted.org/packages/fb/82/bdab26d7438c6791ca31b7c024ca37c1eab8b726ba236129005cd4a06e45/numpy-2.4.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:511dbaf848decaaaf4b4ca48032619fb3138710c4bf7da7617765edad1ef96b0", size = 16684648, upload-time = "2026-05-18T23:34:29.41Z" }, + { url = "https://files.pythonhosted.org/packages/1b/30/a80189bcc7f5e4258b3fbc3968d909d1756f54d023299ecc39ad6fdb9ef8/numpy-2.4.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bf162abab1c1a736333192707cef898e735a5ca00f38f27eeedf44b39d9e85eb", size = 14693902, upload-time = "2026-05-18T23:34:33.013Z" }, + { url = "https://files.pythonhosted.org/packages/97/12/70b5d0d7c15e1ebb8a6a84a8caa1d19e181d84fb58bb6d70aca29099dec1/numpy-2.4.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:043191bfa8eab18c776647b62723ac9dddece59743b13f49b2016094129c2b3f", size = 5198992, upload-time = "2026-05-18T23:34:36.132Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8c/ebd2a8f8a83541f8d38cc5667e8c2b69cecfd30da6e45693e8158857d44b/numpy-2.4.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:6180d8b35af935aed8ece3a85e0a43f87393ae0ac87c8d2c8bd2c993f7270ef3", size = 6546944, upload-time = "2026-05-18T23:34:38.484Z" }, + { url = "https://files.pythonhosted.org/packages/bb/c5/7b863a97a91671a0338f4253bd3b5a3d3852f0692dae91711c9f4a10e787/numpy-2.4.6-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72fbe16c6fac95aedf5937fa873445cec2110be35d8a4e9433d7501fd98dae6b", size = 15669392, upload-time = "2026-05-18T23:34:41.257Z" }, + { url = "https://files.pythonhosted.org/packages/a5/9d/3584b9984ca4c047aea75214ce1a4c4c73d849bd71b604264b7f5653f8a8/numpy-2.4.6-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a7830bab239b79cda9c08c2da014761cafb48da6150e1da17ac06283f43b6089", size = 16633220, upload-time = "2026-05-18T23:34:45.075Z" }, + { url = "https://files.pythonhosted.org/packages/05/ae/7c67fba23bd98caec7c99261f3a16072ade14813486b0282cb29846de832/numpy-2.4.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ef4aea96ce4d3b074422cb4f2f64e216bf9e213004bb58ecfdf50ea02ea8eb9a", size = 17020800, upload-time = "2026-05-18T23:34:49.065Z" }, + { url = "https://files.pythonhosted.org/packages/d9/5d/3b6725cb31d983c5e66916f5d36f6d7e5521129e4c4404d64f918292a5b6/numpy-2.4.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dfa20cc6ca228e6b155b11da03825975ce66aea520985dbbddf0f2a5a495c605", size = 18357600, upload-time = "2026-05-18T23:34:52.709Z" }, + { url = "https://files.pythonhosted.org/packages/f7/da/2ccc6c2fe8898dee01d90c75c5f5f914a23daf99e3e0f59516a08760c8b5/numpy-2.4.6-cp313-cp313-win32.whl", hash = "sha256:56b39e5e0622a09a25bf5baf62f4bcf0cb8a41ae6e2819cf49bbc5a74c083f91", size = 5961134, upload-time = "2026-05-18T23:34:55.618Z" }, + { url = "https://files.pythonhosted.org/packages/b5/cd/9cc4dc876fb065d5c220aae4d5e14826b2715331bb7618ce1fb07a679d99/numpy-2.4.6-cp313-cp313-win_amd64.whl", hash = "sha256:c4fc99836233ea196540b17ab0983aff60ed07941751930f5f4d05bc3b3b7359", size = 12318598, upload-time = "2026-05-18T23:34:58.928Z" }, + { url = "https://files.pythonhosted.org/packages/39/1e/c0bcba1f8694116485fe28fd1be698c278fcda4141c5b0e53a2aed8b12a8/numpy-2.4.6-cp313-cp313-win_arm64.whl", hash = "sha256:a7c711e21628b52034bb5ab8d1bce291f752fcc5e92accc615778acee1ff4778", size = 10222272, upload-time = "2026-05-18T23:35:02.167Z" }, + { url = "https://files.pythonhosted.org/packages/63/6d/cc5619247c8f4204e507f5883528372e4ac4bb189e579fb859a12e480b1f/numpy-2.4.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:112b06a867b235ef466ed3508ddf0238050df9c727cafb5301ac385b899189a1", size = 14821197, upload-time = "2026-05-18T23:35:05.468Z" }, + { url = "https://files.pythonhosted.org/packages/00/58/f1c39161c87d9e9bed660f1ed4bafc0e403d5ec9650b6dd77aead07d489b/numpy-2.4.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:eaf7fa2de5c0be8ae6ff8e9bea2ccd725e980541244521d8d4b5f3354a27babe", size = 5326287, upload-time = "2026-05-18T23:35:08.693Z" }, + { url = "https://files.pythonhosted.org/packages/af/57/3917ab0fd97f271a8694513581b8a36c655f111c446852c302f04ccdb6fc/numpy-2.4.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:7265a2f3d436e54ef9f2b52b5c937e6be778781bd97a590319d7348f1c1ca997", size = 6646763, upload-time = "2026-05-18T23:35:11.459Z" }, + { url = "https://files.pythonhosted.org/packages/eb/0f/037e64c494b67581ae18193d770adef354c41f3f2c8ebf865602d949bf8f/numpy-2.4.6-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f74a575920ab21fe304421a3fc28793d82e299cae9eccb37084e9fc7f3617c20", size = 15728070, upload-time = "2026-05-18T23:35:14.79Z" }, + { url = "https://files.pythonhosted.org/packages/21/a6/5d2bae9c9542eb4df16dc9c46dc79c186e9bad53805dfa5399a6023c6db0/numpy-2.4.6-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ede83e07a75dd06bc501566c1eca2afc0d61677c1472ac9ad93fdee6e638a48d", size = 16681752, upload-time = "2026-05-18T23:35:18.836Z" }, + { url = "https://files.pythonhosted.org/packages/92/14/23d1dfb410ae362cd59ce53e936b1513d545eb40db3949ced632e19a459e/numpy-2.4.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:68bb27509ac1b9a3443094260f6326150663b06abe40b73a2f81160623da5b67", size = 17086024, upload-time = "2026-05-18T23:35:22.52Z" }, + { url = "https://files.pythonhosted.org/packages/4b/6e/23595a2c642cdf3bc567877064bdd7f91c8b0038a4453cf2daf7248eafe9/numpy-2.4.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a0df0043bdb289bde1f62da130d20df23d58b45429f752bc7a8fc5325a225ecd", size = 18403398, upload-time = "2026-05-18T23:35:26.398Z" }, + { url = "https://files.pythonhosted.org/packages/8a/90/0ac3bc947217e66dec77e7cbc6a1979d1af70b6461b82f620d3bccd5e4c8/numpy-2.4.6-cp313-cp313t-win32.whl", hash = "sha256:29a287e0cf63ff528da061de6b9f64a4618da591ca1046aafc54062e40ca7eab", size = 6084971, upload-time = "2026-05-18T23:35:29.387Z" }, + { url = "https://files.pythonhosted.org/packages/77/71/5673e351671a1d2bd6063b91b44f70c0affea7d1516fa7a6572941ba4aa1/numpy-2.4.6-cp313-cp313t-win_amd64.whl", hash = "sha256:25c692919ac5a01f170a3bfcd62d745b24fd095c353d50812637d6fcab442e75", size = 12458532, upload-time = "2026-05-18T23:35:32.175Z" }, + { url = "https://files.pythonhosted.org/packages/3f/88/19d3503c5046e688f049274b27a3ef3d771152fa80d3ba3d01a3dff61abe/numpy-2.4.6-cp313-cp313t-win_arm64.whl", hash = "sha256:1e978ec1e8bd0e0e4de6bb75de9d30cbb74db6b6a2bb727618613703ca0167dd", size = 10291881, upload-time = "2026-05-18T23:35:35.465Z" }, + { url = "https://files.pythonhosted.org/packages/f8/91/3ab2044d05fd16d343c5ac2e69b127f1b2854040dd20b193257c78028bd3/numpy-2.4.6-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06ca2f61ec4385a07a6977c55ba998a4466c123642b4a32694d3128fce18c079", size = 16683458, upload-time = "2026-05-18T23:35:38.353Z" }, + { url = "https://files.pythonhosted.org/packages/8e/62/764ce66fa4147ae6d73071a3abf804ffe606f174618697c571acdf26a7c9/numpy-2.4.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:38efbc8de75c7a0fc1ac190162d892787f3f47b57cc291231aafee36b80982b7", size = 14704559, upload-time = "2026-05-18T23:35:42.14Z" }, + { url = "https://files.pythonhosted.org/packages/60/61/23f27c172f022e04025b7dc2367f4d63c1a398120607ec896228649a6f48/numpy-2.4.6-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:d581b735e177fdcdce6fed8e7e8880a3fb6ee4e3653a3ac6af01c6f4c03effc5", size = 5209716, upload-time = "2026-05-18T23:35:45.377Z" }, + { url = "https://files.pythonhosted.org/packages/03/71/21cf70dc6ea3e3acb95fc53a265b2fc248b981f0194ceb5b475271b8809d/numpy-2.4.6-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:0a041d3d761dc3c35cc56ce0351506a02bcbc25f7b169f652435141a17db9096", size = 6543947, upload-time = "2026-05-18T23:35:47.926Z" }, + { url = "https://files.pythonhosted.org/packages/d5/91/64288395ee1799bd2e0b04a305dce9666da90c961e1f3fe982a05ee1c036/numpy-2.4.6-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40fdc1ae7125e518ea98e53e69a4ebc27e1fd50510c47b7ea130cf21e5e1d42b", size = 15685197, upload-time = "2026-05-18T23:35:50.863Z" }, + { url = "https://files.pythonhosted.org/packages/f3/eb/ebffaa97dc55502df69584a8f0dcf07f69a3e0b3e2323670a2722db9aa39/numpy-2.4.6-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a2c306dea656c12c68f51f4cea133cbe78ca7435eb28c735eac1d3ebe73be6e8", size = 16638245, upload-time = "2026-05-18T23:35:54.752Z" }, + { url = "https://files.pythonhosted.org/packages/b8/0b/54f9da33128d7e350fab89c7455902eeae70349ee52bddb448dc4a576f45/numpy-2.4.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:33111801a01c12a8a1e3721f0a9232f8cfc8ae2c6b7098167e6f623c6073f402", size = 17036587, upload-time = "2026-05-18T23:35:58.355Z" }, + { url = "https://files.pythonhosted.org/packages/b6/f0/fdebc1052db1cc37c64beb22072d67cd6d1c71adca1299f53dec2b5e20d3/numpy-2.4.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ae506e6902902557576a26ff33eda8695e7ecb3cb36c3b573a0765dee114ebdb", size = 18363226, upload-time = "2026-05-18T23:36:02.845Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b4/298628d98c72b57e57f7165ae6a481a1deaf6f3c28262a6e4c739c275930/numpy-2.4.6-cp314-cp314-win32.whl", hash = "sha256:aaf159caa35993cb1f56fb9b8e4610d35758e7ca005412eb1daa856a78c9c4b1", size = 6010196, upload-time = "2026-05-18T23:36:05.92Z" }, + { url = "https://files.pythonhosted.org/packages/df/ac/46de6dda46478f7942f839e094970be2d4a861e005c4b3bf07c92e291a09/numpy-2.4.6-cp314-cp314-win_amd64.whl", hash = "sha256:b507f5c4c1d508876d1819b6bf9a49d365b96320b5d4993426b33a23ca4b8261", size = 12450334, upload-time = "2026-05-18T23:36:09.107Z" }, + { url = "https://files.pythonhosted.org/packages/78/92/b8b798ac784102c0da830d2257d59358e3d3d90d1e2b3f2575dad976c5cf/numpy-2.4.6-cp314-cp314-win_arm64.whl", hash = "sha256:6f41ae150c4e32db4f3310cdaf64b1593a03dbabe29eec77fc9b50fe64061df6", size = 10495678, upload-time = "2026-05-18T23:36:12.766Z" }, + { url = "https://files.pythonhosted.org/packages/30/34/ec28d1aa8115971537c01469ab2011ee96827930f0a124de1000cc2a7ed7/numpy-2.4.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ece3d2cfe132e7d51f44a832b303895e6f2d499c5e74dfbdb06ee246147a304a", size = 14823672, upload-time = "2026-05-18T23:36:16.473Z" }, + { url = "https://files.pythonhosted.org/packages/16/bd/f6d1fede4e54e8042a7ff97bb495510f3c220f94bcd9e8b228e87c92cc0d/numpy-2.4.6-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:e3e5193ef5a3dc73bceee50f7fdc2c90dbb76c42df8d8fae3d1067a583df579e", size = 5328731, upload-time = "2026-05-18T23:36:19.767Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f0/e105b9e2fd728a9910103884decd6951d9dd73896b914a98d9a231de02ee/numpy-2.4.6-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:17f9ade344e7d9b464a084d69bcf18fc691cb1db67c62ed80820bf4926d78f0e", size = 6649805, upload-time = "2026-05-18T23:36:22.266Z" }, + { url = "https://files.pythonhosted.org/packages/82/dd/1206a7ca6ab15e3f02069707ca96222e202af681bb73756da7527f3cb837/numpy-2.4.6-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cd5ffd25db4e7ba6a375693b3fc0fc1791ec636c17db3720da19bde7180ec43", size = 15730496, upload-time = "2026-05-18T23:36:25.713Z" }, + { url = "https://files.pythonhosted.org/packages/51/e7/38d3ea825dcab85a591734decb2f6c67caa7c8367d374df1a1c3842f9b07/numpy-2.4.6-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d92c3819208a60205a12a245c91ad70cb0a85336659b19b834205573ac8456e", size = 16679616, upload-time = "2026-05-18T23:36:29.652Z" }, + { url = "https://files.pythonhosted.org/packages/93/b7/caabfdf53edf663e0b4eb74d7d405d83baef09eb5e83bcd32d601d72b93e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e85b752a1e912b70eaad4fafbd4d1238007ab221de2009b9a2f5ae7461239895", size = 17085145, upload-time = "2026-05-18T23:36:33.449Z" }, + { url = "https://files.pythonhosted.org/packages/f9/45/68d7c33a6bcf3e5aa3bdbd57a367e6f615286dfd6482f97e8ffeb734306e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:29cb7f67d10b479ff07c17d33e39f78c07f71c40ef30d63c153d340e96cd3fb4", size = 18403813, upload-time = "2026-05-18T23:36:37.369Z" }, + { url = "https://files.pythonhosted.org/packages/9c/50/0753655aa844c99cd9e018aacf76f130f1bd81d881bb74bc0aef5d73a8ba/numpy-2.4.6-cp314-cp314t-win32.whl", hash = "sha256:260a5d70215b61ab4fadf5c7baacd64821842975eea312125ed3c39a6391b063", size = 6156982, upload-time = "2026-05-18T23:36:40.817Z" }, + { url = "https://files.pythonhosted.org/packages/b2/d4/7c67becf668f973cb490cec3e98dfd799d866f9c989a54d355672cfa0db6/numpy-2.4.6-cp314-cp314t-win_amd64.whl", hash = "sha256:81a1cca95ed5bb92aa8b10dd2cdc9a0d3853a50fad926c28b5d7e8ea54389627", size = 12638908, upload-time = "2026-05-18T23:36:43.996Z" }, + { url = "https://files.pythonhosted.org/packages/43/bb/e1c71a4295b1b1d1393d50dbb4f2a36283c6859d9d3892e84f00ec5a91d5/numpy-2.4.6-cp314-cp314t-win_arm64.whl", hash = "sha256:0c9136e14ed34a9e343a31c533d78a9813a69a3148332bce5e9821cb2f996e66", size = 10565867, upload-time = "2026-05-18T23:36:47.114Z" }, + { url = "https://files.pythonhosted.org/packages/de/12/b422cc84439adc0d00de605bf4a308890ae5c26f2c71fbd73e5d08fbb0dd/numpy-2.4.6-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:55cced7c52e981362f708ad635198e97a752dfba412cc03c23bbf3bd8d5cd662", size = 16847511, upload-time = "2026-05-18T23:36:50.673Z" }, + { url = "https://files.pythonhosted.org/packages/44/53/f481bef68011740f8849418d82db07230e825013f31f4eef5ba5b805316a/numpy-2.4.6-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d6da64deb6b8ed903e7560180a92f2d804ee1ba5eeb849ac2748b8c1aba1f6d7", size = 14889064, upload-time = "2026-05-18T23:36:53.879Z" }, + { url = "https://files.pythonhosted.org/packages/7f/57/42ed575c10ced8af951d426bc4e1f8aff16fd851db33f067036215a7f860/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:68a5124b13fa6cc2086764a20005d30bc0548146f7f5322f02fce212ca14317f", size = 5394157, upload-time = "2026-05-18T23:36:57.194Z" }, + { url = "https://files.pythonhosted.org/packages/6a/ef/f66cc724fcc36c1e364c67f51ae9146090b8b584f27d58b97fdae3edd737/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:948424b06129ce883307e8cff868c31396d8dc7630a59c61d70d98dbe70f222c", size = 6708728, upload-time = "2026-05-18T23:36:59.575Z" }, + { url = "https://files.pythonhosted.org/packages/1a/9c/c531f2293b91265d8b48e9b329f54fdd7ffae73cb4134ea10cca4237e9cc/numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5dbbdb29840ca3d91ee0fece42fc29278886d908280bfec0a5846c6f901a3eb0", size = 15798374, upload-time = "2026-05-18T23:37:02.674Z" }, + { url = "https://files.pythonhosted.org/packages/1a/b0/413077f6b1153ed3cba361401c6783bbad6114804a000cc22eb71c13e190/numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8ad03c0965fb3c692200e74d458ca28c1dbb4ce96f9a479a8aa041ad5fabca02", size = 16747286, upload-time = "2026-05-18T23:37:06.327Z" }, + { url = "https://files.pythonhosted.org/packages/15/ce/e5ec180bc41812edcd8daeb8639d205622c0e8c02259d8ab25a0201b3c2a/numpy-2.4.6-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:2803abfebfc990042cd494d8ce2d5f82e9d847af6d35ec486923aa19dbad5e73", size = 12504263, upload-time = "2026-05-18T23:37:09.715Z" }, ] [[package]] @@ -1671,82 +1732,79 @@ wheels = [ [[package]] name = "onnxruntime" -version = "1.24.4" +version = "1.26.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "flatbuffers" }, { name = "numpy" }, { name = "packaging" }, { name = "protobuf" }, - { name = "sympy" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/60/69/6c40720201012c6af9aa7d4ecdd620e521bd806dc6269d636fdd5c5aeebe/onnxruntime-1.24.4-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:0bdfce8e9a6497cec584aab407b71bf697dac5e1b7b7974adc50bf7533bdb3a2", size = 17332131, upload-time = "2026-03-17T22:05:49.005Z" }, - { url = "https://files.pythonhosted.org/packages/38/e9/8c901c150ce0c368da38638f44152fb411059c0c7364b497c9e5c957321a/onnxruntime-1.24.4-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:046ff290045a387676941a02a8ae5c3ebec6b4f551ae228711968c4a69d8f6b7", size = 15152472, upload-time = "2026-03-17T22:03:26.176Z" }, - { url = "https://files.pythonhosted.org/packages/d5/b6/7a4df417cdd01e8f067a509e123ac8b31af450a719fa7ed81787dd6057ec/onnxruntime-1.24.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e54ad52e61d2d4618dcff8fa1480ac66b24ee2eab73331322db1049f11ccf330", size = 17222993, upload-time = "2026-03-17T22:04:34.485Z" }, - { url = "https://files.pythonhosted.org/packages/dd/59/8febe015f391aa1757fa5ba82c759ea4b6c14ef970132efb5e316665ba61/onnxruntime-1.24.4-cp311-cp311-win_amd64.whl", hash = "sha256:b43b63eb24a2bc8fc77a09be67587a570967a412cccb837b6245ccb546691153", size = 12594863, upload-time = "2026-03-17T22:05:38.749Z" }, - { url = "https://files.pythonhosted.org/packages/32/84/4155fcd362e8873eb6ce305acfeeadacd9e0e59415adac474bea3d9281bb/onnxruntime-1.24.4-cp311-cp311-win_arm64.whl", hash = "sha256:e26478356dba25631fb3f20112e345f8e8bf62c499bb497e8a559f7d69cf7e7b", size = 12259895, upload-time = "2026-03-17T22:05:28.812Z" }, - { url = "https://files.pythonhosted.org/packages/d7/38/31db1b232b4ba960065a90c1506ad7a56995cd8482033184e97fadca17cc/onnxruntime-1.24.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:cad1c2b3f455c55678ab2a8caa51fb420c25e6e3cf10f4c23653cdabedc8de78", size = 17341875, upload-time = "2026-03-17T22:05:51.669Z" }, - { url = "https://files.pythonhosted.org/packages/aa/60/c4d1c8043eb42f8a9aa9e931c8c293d289c48ff463267130eca97d13357f/onnxruntime-1.24.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1a5c5a544b22f90859c88617ecb30e161ee3349fcc73878854f43d77f00558b5", size = 15172485, upload-time = "2026-03-17T22:03:32.182Z" }, - { url = "https://files.pythonhosted.org/packages/6d/ab/5b68110e0460d73fad814d5bd11c7b1ddcce5c37b10177eb264d6a36e331/onnxruntime-1.24.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0d640eb9f3782689b55cfa715094474cd5662f2f137be6a6f847a594b6e9705c", size = 17244912, upload-time = "2026-03-17T22:04:37.251Z" }, - { url = "https://files.pythonhosted.org/packages/8b/f4/6b89e297b93704345f0f3f8c62229bee323ef25682a3f9b4f89a39324950/onnxruntime-1.24.4-cp312-cp312-win_amd64.whl", hash = "sha256:535b29475ca42b593c45fbb2152fbf1cdf3f287315bf650e6a724a0a1d065cdb", size = 12596856, upload-time = "2026-03-17T22:05:41.224Z" }, - { url = "https://files.pythonhosted.org/packages/43/06/8b8ec6e9e6a474fcd5d772453f627ad4549dfe3ab8c0bf70af5afcde551b/onnxruntime-1.24.4-cp312-cp312-win_arm64.whl", hash = "sha256:e6214096e14b7b52e3bee1903dc12dc7ca09cb65e26664668a4620cc5e6f9a90", size = 12270275, upload-time = "2026-03-17T22:05:31.132Z" }, - { url = "https://files.pythonhosted.org/packages/e9/f0/8a21ec0a97e40abb7d8da1e8b20fb9e1af509cc6d191f6faa75f73622fb2/onnxruntime-1.24.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e99a48078baaefa2b50fe5836c319499f71f13f76ed32d0211f39109147a49e0", size = 17341922, upload-time = "2026-03-17T22:03:56.364Z" }, - { url = "https://files.pythonhosted.org/packages/8b/25/d7908de8e08cee9abfa15b8aa82349b79733ae5865162a3609c11598805d/onnxruntime-1.24.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc4aaed1e5e1aaacf2343c838a30a7c3ade78f13eeb16817411f929d04040a13", size = 15172290, upload-time = "2026-03-17T22:03:37.124Z" }, - { url = "https://files.pythonhosted.org/packages/7f/72/105ec27a78c5aa0154a7c0cd8c41c19a97799c3b12fc30392928997e3be3/onnxruntime-1.24.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e30c972bc02e072911aabb6891453ec73795386c0af2b761b65444b8a4c4745f", size = 17244738, upload-time = "2026-03-17T22:04:40.625Z" }, - { url = "https://files.pythonhosted.org/packages/05/fb/a592736d968c2f58e12de4d52088dda8e0e724b26ad5c0487263adb45875/onnxruntime-1.24.4-cp313-cp313-win_amd64.whl", hash = "sha256:3b6ba8b0181a3aa88edab00eb01424ffc06f42e71095a91186c2249415fcff93", size = 12597435, upload-time = "2026-03-17T22:05:43.826Z" }, - { url = "https://files.pythonhosted.org/packages/ad/04/ae2479e9841b64bd2eb44f8a64756c62593f896514369a11243b1b86ca5c/onnxruntime-1.24.4-cp313-cp313-win_arm64.whl", hash = "sha256:71d6a5c1821d6e8586a024000ece458db8f2fc0ecd050435d45794827ce81e19", size = 12269852, upload-time = "2026-03-17T22:05:33.353Z" }, - { url = "https://files.pythonhosted.org/packages/b4/af/a479a536c4398ffaf49fbbe755f45d5b8726bdb4335ab31b537f3d7149b8/onnxruntime-1.24.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1700f559c8086d06b2a4d5de51e62cb4ff5e2631822f71a36db8c72383db71ee", size = 15176861, upload-time = "2026-03-17T22:03:40.143Z" }, - { url = "https://files.pythonhosted.org/packages/be/13/19f5da70c346a76037da2c2851ecbf1266e61d7f0dcdb887c667210d4608/onnxruntime-1.24.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c74e268dc808e61e63784d43f9ddcdaf50a776c2819e8bd1d1b11ef64bf7e36", size = 17247454, upload-time = "2026-03-17T22:04:46.643Z" }, - { url = "https://files.pythonhosted.org/packages/89/db/b30dbbd6037847b205ab75d962bc349bf1e46d02a65b30d7047a6893ffd6/onnxruntime-1.24.4-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:fbff2a248940e3398ae78374c5a839e49a2f39079b488bc64439fa0ec327a3e4", size = 17343300, upload-time = "2026-03-17T22:03:59.223Z" }, - { url = "https://files.pythonhosted.org/packages/61/88/1746c0e7959961475b84c776d35601a21d445f463c93b1433a409ec3e188/onnxruntime-1.24.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e2b7969e72d8cb53ffc88ab6d49dd5e75c1c663bda7be7eb0ece192f127343d1", size = 15175936, upload-time = "2026-03-17T22:03:43.671Z" }, - { url = "https://files.pythonhosted.org/packages/5f/ba/4699cde04a52cece66cbebc85bd8335a0d3b9ad485abc9a2e15946a1349d/onnxruntime-1.24.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14ed1f197fab812b695a5eaddb536c635e58a2fbbe50a517c78f082cc6ce9177", size = 17246432, upload-time = "2026-03-17T22:04:49.58Z" }, - { url = "https://files.pythonhosted.org/packages/ef/60/4590910841bb28bd3b4b388a9efbedf4e2d2cca99ddf0c863642b4e87814/onnxruntime-1.24.4-cp314-cp314-win_amd64.whl", hash = "sha256:311e309f573bf3c12aa5723e23823077f83d5e412a18499d4485c7eb41040858", size = 12903276, upload-time = "2026-03-17T22:05:46.349Z" }, - { url = "https://files.pythonhosted.org/packages/7f/6f/60e2c0acea1e1ac09b3e794b5a19c166eebf91c0b860b3e6db8e74983fda/onnxruntime-1.24.4-cp314-cp314-win_arm64.whl", hash = "sha256:3f0b910e86b759a4732663ec61fd57ac42ee1b0066f68299de164220b660546d", size = 12594365, upload-time = "2026-03-17T22:05:35.795Z" }, - { url = "https://files.pythonhosted.org/packages/cf/68/0c05d10f8f6c40fe0912ebec0d5a33884aaa2af2053507e864dab0883208/onnxruntime-1.24.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa12ddc54c9c4594073abcaa265cd9681e95fb89dae982a6f508a794ca42e661", size = 15176889, upload-time = "2026-03-17T22:03:48.021Z" }, - { url = "https://files.pythonhosted.org/packages/6c/1d/1666dc64e78d8587d168fec4e3b7922b92eb286a2ddeebcf6acb55c7dc82/onnxruntime-1.24.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1cc6a518255f012134bc791975a6294806be9a3b20c4a54cca25194c90cf731", size = 17247021, upload-time = "2026-03-17T22:04:52.377Z" }, + { url = "https://files.pythonhosted.org/packages/d4/81/29a9eb470994a75eb7b3ccf32be314d7c66675a00ac7b50294816cc2db27/onnxruntime-1.26.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:ee1109ef4ef27cad90e823399e61e03b3c6c7bfe0fb820b4baf3678c15be8b3c", size = 18005108, upload-time = "2026-05-08T19:08:11.728Z" }, + { url = "https://files.pythonhosted.org/packages/66/c7/73efa6c8a4000c38fcc14947d84f234a17e5d66f203b37b7f1ad4a7b46eb/onnxruntime-1.26.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:35c7c7b0ac2e02001d28fab6c9fc24e9abc5e6faa35e6e19c63cecf1406ba89f", size = 16043752, upload-time = "2026-05-08T19:07:10.707Z" }, + { url = "https://files.pythonhosted.org/packages/b6/3f/8de630f595daf6ce884d4dd95afd2a60e70ec6572e52bfee3aa2229befab/onnxruntime-1.26.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11a8df4dcfe9ad5ff0bd71a7571dbed019fabc7594676c89fe8b86ea029c246f", size = 18176043, upload-time = "2026-05-08T19:07:33.735Z" }, + { url = "https://files.pythonhosted.org/packages/9c/21/9f041de20787cd85498bd48e0ec4d098bf2a6c486e25b24b8dae1bf492b2/onnxruntime-1.26.0-cp311-cp311-win_amd64.whl", hash = "sha256:e6456718125fd777c673f3b78d4a9ab58d6adea641e9afae85ee6444f0e0e9a9", size = 13023165, upload-time = "2026-05-08T19:08:00.633Z" }, + { url = "https://files.pythonhosted.org/packages/0e/82/3b9fe0ead2557cc3adf74c74c141bd1c7c4c6a9548c610af37df199f4512/onnxruntime-1.26.0-cp311-cp311-win_arm64.whl", hash = "sha256:cd920e45b730e4a87833e2910d8ca375aaca9da6ccc09e24bce463b3356d637f", size = 12789514, upload-time = "2026-05-08T19:07:49.433Z" }, + { url = "https://files.pythonhosted.org/packages/81/b1/d111b1df656761f980d9e298a60039a9cb66036b1d039e777537743d0ac3/onnxruntime-1.26.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:05b028781b322ad74b57ce5b50aa5280bb1fe96ceec334628ade681e0b24c1ac", size = 18016624, upload-time = "2026-05-12T00:41:01.735Z" }, + { url = "https://files.pythonhosted.org/packages/f6/a0/3f9d896a0385a36bd04345d6d0b802821a5782adde562e7e135f6bb71c73/onnxruntime-1.26.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:91f2bb870a4b9224eba0a6728c1fa7a9e552b8e59e1083c51fbbc3d013f2b5c0", size = 16052692, upload-time = "2026-05-08T19:07:13.829Z" }, + { url = "https://files.pythonhosted.org/packages/7c/43/2a4e04f8dbeffad19bbcced4bcd4289bf478921518437404d6b92bdf213b/onnxruntime-1.26.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9b6dd70599005bd1bf29779f04a91978b92b5e719c11a20068a8f8e535f725b6", size = 18185439, upload-time = "2026-05-08T19:07:36.299Z" }, + { url = "https://files.pythonhosted.org/packages/44/fc/026d0a7162b9c2153dac292baea9e027c42304dc1d9dc6f8ff5b4cfbaedd/onnxruntime-1.26.0-cp312-cp312-win_amd64.whl", hash = "sha256:a26374dc7fbcaae593601086b242120e13f2310558df0991da6dd8b8fac00414", size = 13026427, upload-time = "2026-05-08T19:08:03.503Z" }, + { url = "https://files.pythonhosted.org/packages/3e/27/1dcf88e45e4c69db5f7b106f2dacc3801ba98994e082ca03e1dfdf7bfe57/onnxruntime-1.26.0-cp312-cp312-win_arm64.whl", hash = "sha256:54a8053410fd31fd66469bd754fcfe8a4df9f7eb44756b4b5479bf50c842d948", size = 12796647, upload-time = "2026-05-08T19:07:52.108Z" }, + { url = "https://files.pythonhosted.org/packages/cf/a2/c801242685e0ce48a4ca51dfafbb588765e0446397e123be53ba5598f3f5/onnxruntime-1.26.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:ccce19c5f771b8268902f77d9fed9e88f9499465d6780808faa6611a789d33f0", size = 18016563, upload-time = "2026-05-08T19:07:28.081Z" }, + { url = "https://files.pythonhosted.org/packages/e2/64/0492c0b1db04e29b2630c87cfa36f9d6872b1ca8614b90c5cad58fac7d76/onnxruntime-1.26.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bdbed8cf3b672b66acb032f33a253bc27f42bce6ece48ae3fab4fa483a5e96e0", size = 16052634, upload-time = "2026-05-08T19:07:16.885Z" }, + { url = "https://files.pythonhosted.org/packages/3d/26/4d09ddc755a84fc8d5e192991626b0e0680e8f6c5d58f4f1d05c42bc48cf/onnxruntime-1.26.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c07af6fc6d5557835f2b6ee7a96d8b3235d0c57a8e230efdedaee106a8a3cbc6", size = 18185632, upload-time = "2026-05-08T19:07:38.756Z" }, + { url = "https://files.pythonhosted.org/packages/77/89/3e52249aa08fa301e217ecba07b5246a8338fa2b401e109326e3fc5be0f9/onnxruntime-1.26.0-cp313-cp313-win_amd64.whl", hash = "sha256:61bec80655efa460591c2bc655392d57d2650ce85533a6b9b3b7a790d7ea7916", size = 13026751, upload-time = "2026-05-08T19:08:06.2Z" }, + { url = "https://files.pythonhosted.org/packages/06/b3/c1c8782b14af6797c303de132d6eef26a9fb80dfacd3750ce57911d11c6b/onnxruntime-1.26.0-cp313-cp313-win_arm64.whl", hash = "sha256:a6677545ff451e3539a02746d2f207d8c5baa4a0a818886bb9d6a6eb9511ee89", size = 12796807, upload-time = "2026-05-08T19:07:54.879Z" }, + { url = "https://files.pythonhosted.org/packages/c3/f5/47b0676408abec652c14b84d7173e389837832d850c24f87184277313e8d/onnxruntime-1.26.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e016edc15d3c19f36807e1c6b10be5b27807688c32720f91b5ae480a95215d0", size = 16057265, upload-time = "2026-05-08T19:07:19.603Z" }, + { url = "https://files.pythonhosted.org/packages/3b/45/33ab6deeef010ca844c877dd618cebc079590bbe52d2a3678e7223b1b908/onnxruntime-1.26.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f5fc48a91a046a6a5c9b147f83fb41d65d24d24923373b222cdd248f0f4f4aac", size = 18197590, upload-time = "2026-05-08T19:07:41.422Z" }, + { url = "https://files.pythonhosted.org/packages/40/89/17546c1c20f6bfc3ae41c22152378a26edfea918af3129e2139dcd7c99f3/onnxruntime-1.26.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:33a791f31432a3af1a96db5e54818b37aba5e5eefc2e6af5794c10a9118a9993", size = 18019724, upload-time = "2026-05-08T19:07:30.723Z" }, + { url = "https://files.pythonhosted.org/packages/bb/24/89457a35f6af29538a76647f2c18c3a28277e6c19234c847e7b4b7c19860/onnxruntime-1.26.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e90c00732c4553618103149d93f688e8c3063017938f8983e21a71d9f3b6d22e", size = 16054821, upload-time = "2026-05-08T19:07:22.348Z" }, + { url = "https://files.pythonhosted.org/packages/12/f9/15b2e1815cf570d238e0135529f80d2dce64e8e8818a1489cae83823c5c6/onnxruntime-1.26.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01498e80ba8988428d08c2d51b1338f89e3de2a93e6ffe555f79c68f26a5c06b", size = 18185815, upload-time = "2026-05-08T19:07:44.179Z" }, + { url = "https://files.pythonhosted.org/packages/d7/65/2e11055faf015e4b07f45b513fa49b391baf2e19d92d77d73ebee13c1004/onnxruntime-1.26.0-cp314-cp314-win_amd64.whl", hash = "sha256:7ead61450d8405167c87dd3a31d8da1d576b490a57dab1aa8b82a7da6825f5aa", size = 13349887, upload-time = "2026-05-08T19:08:08.671Z" }, + { url = "https://files.pythonhosted.org/packages/19/e4/0f9d1a5718b1781c610c1e354765a3820597081754277a6a9a2b50705702/onnxruntime-1.26.0-cp314-cp314-win_arm64.whl", hash = "sha256:31d71a53490e46910877d0902b5ad99c69a5955e5c7ea6c82863519410e1ba7c", size = 13140121, upload-time = "2026-05-08T19:07:57.804Z" }, + { url = "https://files.pythonhosted.org/packages/1c/42/3b8e635f067d06d9f45bede470b8d539d101a4166c272213158dfd08b6ce/onnxruntime-1.26.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d7b6d258fb78fdfcf049795bcfaa74dcb90ae7baa277afd21e6fd28b83f2c496", size = 16057240, upload-time = "2026-05-08T19:07:25.163Z" }, + { url = "https://files.pythonhosted.org/packages/93/99/f2be40a31b908d96b861ae0ce98582fa376c18a7f816b9d5eb4cd6aa0a4c/onnxruntime-1.26.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4eefd386a45202aefb7a5132b94f32df9d506c9edcc7faf2fc60d65183f4b183", size = 18197382, upload-time = "2026-05-08T19:07:46.965Z" }, ] [[package]] name = "onnxruntime-gpu" -version = "1.24.4" +version = "1.26.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "flatbuffers" }, { name = "numpy" }, { name = "packaging" }, { name = "protobuf" }, - { name = "sympy" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/9f/13/e080d758f2b60f71abe518c707135fb121d6a3019e0761ead89b5283ac3d/onnxruntime_gpu-1.24.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c2a698659271c28220b3f56fe9b63f70eae3b3c36afa544201bf750b929a36dc", size = 252761835, upload-time = "2026-03-17T22:03:45.584Z" }, - { url = "https://files.pythonhosted.org/packages/d2/07/036825cbe30f91ea8574a18a759beccd0ea31b7b71e17f6a9ee9304b51d2/onnxruntime_gpu-1.24.4-cp311-cp311-win_amd64.whl", hash = "sha256:1a799a16e5f1ff4d6a9e5f72d750849ab0fe534da8d323ae4a5d8d8bb7daeca8", size = 207193563, upload-time = "2026-03-17T21:58:28.097Z" }, - { url = "https://files.pythonhosted.org/packages/d0/2c/5b3fd4748cf7ed291eae541a37e426efc20ea04cb6e6a05768304ab0aa41/onnxruntime_gpu-1.24.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eb0e38f0c1ef3b76ae0081c8e51eed20dd8925aa916f0fc6f9b8b17d05610e99", size = 252765531, upload-time = "2026-03-17T22:03:57.528Z" }, - { url = "https://files.pythonhosted.org/packages/f2/86/70cecfdab1e963cc7f8c11e72040dfcd5cff85b1de2de74deba9611e0059/onnxruntime_gpu-1.24.4-cp312-cp312-win_amd64.whl", hash = "sha256:da5c1e327d8e119a831be2790e69f93cf6daab9145ed0aca7577f412a620f709", size = 207197978, upload-time = "2026-03-17T21:58:38.43Z" }, - { url = "https://files.pythonhosted.org/packages/be/4e/56d11203d7a35e7d6a5ea735f5fecb8673537038c07323e8d3090a896547/onnxruntime_gpu-1.24.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bbdaa73f9055fb2a177425edbed651a1843a6239f9d5430e284f4e5f65440a33", size = 252763446, upload-time = "2026-03-17T22:04:09.515Z" }, - { url = "https://files.pythonhosted.org/packages/fa/bc/35f3a37226d7a28c84b8b456f52237ccd39eb7111114bcf9ac340178e1ec/onnxruntime_gpu-1.24.4-cp313-cp313-win_amd64.whl", hash = "sha256:6be8bf2048777c517fca33eb61e114969fa326619feaa789d8c75f24337ea762", size = 207198775, upload-time = "2026-03-17T21:58:48.768Z" }, - { url = "https://files.pythonhosted.org/packages/37/83/0c851882051b38f245f44b4a51d6232b95b8cd5d334b2c1260f2d796834f/onnxruntime_gpu-1.24.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e4b348a078ced73fc577d21b83992fd2187edd10c233729c8d01b000b8543525", size = 252774594, upload-time = "2026-03-17T22:04:24.957Z" }, - { url = "https://files.pythonhosted.org/packages/3e/5b/82b27f766b64f97c9a98b772dc07b608e900bd2faafdfa176b86d20be7f8/onnxruntime_gpu-1.24.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:af9dd7ef92d94c75e5523cf070e180f3d8cdbb2fc007dcea97ba71b03e3b96d6", size = 252765395, upload-time = "2026-03-17T22:04:37.305Z" }, - { url = "https://files.pythonhosted.org/packages/5d/95/fa8c48e03790c979167d08164b34a8442c7074bca4c7253b4455497025de/onnxruntime_gpu-1.24.4-cp314-cp314-win_amd64.whl", hash = "sha256:4dde3d2f1039060c42b12fd446fc0da5b836cc65dceb4020ca60a04cffa1d90d", size = 209597109, upload-time = "2026-03-17T21:58:58.136Z" }, - { url = "https://files.pythonhosted.org/packages/1a/98/7707edefcecf69d6c45b83a83f13ac58257017b4eaf58772668d302f849f/onnxruntime_gpu-1.24.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:097c6f53e99ee35f21d0fdba76ca283b92465a0e364c6f0209cb9653c424e2a4", size = 252776951, upload-time = "2026-03-17T22:04:49.715Z" }, + { url = "https://files.pythonhosted.org/packages/dc/0f/696b4f94a282952239ffed39db78cb17a00ad993acd929cfac010a09759b/onnxruntime_gpu-1.26.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4fa231294c2643911d2a7d16469c4808b0bdcdc4b5f4063d3a53744ce25b683a", size = 276956564, upload-time = "2026-05-08T19:15:35.133Z" }, + { url = "https://files.pythonhosted.org/packages/ef/26/a417b7a1cdbbf56a389bfcd399255be23f30e5721e3e519472fe8dde9c99/onnxruntime_gpu-1.26.0-cp311-cp311-win_amd64.whl", hash = "sha256:cc5329aad02d9745cc3ae9cdb185bfa1aad242a7bf89b8c471280002ec40f98a", size = 226539455, upload-time = "2026-05-08T19:09:24.631Z" }, + { url = "https://files.pythonhosted.org/packages/94/fd/59bee7cffaa435da44fefdeb63e29c61de4dbfa4b279852f59cd02c042ae/onnxruntime_gpu-1.26.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3c01119ed4d9449d60367fa8ccffcd02bd3fe736754284e4b198d131f54edad6", size = 276971796, upload-time = "2026-05-08T19:15:46.192Z" }, + { url = "https://files.pythonhosted.org/packages/a4/e4/9b378a5466ea0bed65e5beb8e09254973c580a6522810a38afbcc45e5105/onnxruntime_gpu-1.26.0-cp312-cp312-win_amd64.whl", hash = "sha256:5f49c44689894650990e4c8a857d2edafc276fbd79bba57ceb224bd18d25d491", size = 226548963, upload-time = "2026-05-08T19:09:34.925Z" }, + { url = "https://files.pythonhosted.org/packages/dd/97/fe8979f44b9275654b42f7bb556e30789b71a1b22998c83b540df2b1b774/onnxruntime_gpu-1.26.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cfda2fad535595bfc3e570eb588092717711dcb2957656d814695e0c9ceb1508", size = 276974871, upload-time = "2026-05-08T19:15:58.052Z" }, + { url = "https://files.pythonhosted.org/packages/67/3f/59f1777a394625ecc9a85636de57dc47c25dbb5f888da050f1463955a0ce/onnxruntime_gpu-1.26.0-cp313-cp313-win_amd64.whl", hash = "sha256:6ab9f9c741d2e239b2e321ab0d389c04329d4ab7f11e3b92dd3aa7db1c59dee4", size = 226548083, upload-time = "2026-05-08T19:09:44.408Z" }, + { url = "https://files.pythonhosted.org/packages/89/96/360328e3c463f7ea08e853c4239c397e83363dd0204de71a710dc1a544bd/onnxruntime_gpu-1.26.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bcf6f347cad9f88a9a625c2b352cf9de927528aedb627ebbc089a201f1990b94", size = 276992052, upload-time = "2026-05-08T19:16:09.892Z" }, + { url = "https://files.pythonhosted.org/packages/fd/c8/aa2dc0e79bba577f37d5448bcb32fea79977e07506684d8138c19a0f1077/onnxruntime_gpu-1.26.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e6e4fb1ec9ae1cf456534d9115f106ab2a1ae96fa513b4ed0f4795302b4a2c6", size = 276978254, upload-time = "2026-05-08T19:16:22.096Z" }, + { url = "https://files.pythonhosted.org/packages/41/e7/923298431e669567d7ccc2a4c898b6534a47641a051569fd97165fe6d9b8/onnxruntime_gpu-1.26.0-cp314-cp314-win_amd64.whl", hash = "sha256:3e592439b0183d303c2374517b5b392599a3d50b2dc9de949b9b15731ac921c9", size = 229142768, upload-time = "2026-05-08T19:09:54.589Z" }, + { url = "https://files.pythonhosted.org/packages/97/91/93ffe5431d154989f5e04864a25a97eea480997d771232bcbbc538188241/onnxruntime_gpu-1.26.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:56dc7b73954ff4bdc71f5b8ab306b6f61be5d007881b6ef423a609e2b9cd088b", size = 276991545, upload-time = "2026-05-08T19:16:33.347Z" }, ] [[package]] name = "onnxruntime-migraphx" -version = "1.24.2" +version = "1.25.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "flatbuffers" }, { name = "numpy" }, { name = "packaging" }, { name = "protobuf" }, - { name = "sympy" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/dd/da/ca7ebc1a8d1193c97ceb9a05fad50f675eb955dc51beb7eb9ba89c8e7db0/onnxruntime_migraphx-1.24.2-cp311-cp311-manylinux_2_34_x86_64.whl", hash = "sha256:a2b434fb8880cac2b268950bdf279f33741d29c1f1c5461d27af835e8e288043", size = 20339710, upload-time = "2026-02-21T07:25:13.17Z" }, - { url = "https://files.pythonhosted.org/packages/fa/2e/8c83ec45a9365b4256495ca55eea30da7f03b02177b6da423c7da1ff5f6a/onnxruntime_migraphx-1.24.2-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:ec814818da952bda3062e26f56c88bb713c00491ef91f86716c8d7346f9bc31b", size = 20341883, upload-time = "2026-02-21T07:25:17.86Z" }, - { url = "https://files.pythonhosted.org/packages/9f/52/4776ac68dbc46ca02c9a14cc9e5c496017f47a18cedf606cc38f4911b96a/onnxruntime_migraphx-1.24.2-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:20e497538362170af639b03a40249d7ed61b873ac354f20d732b90252206e320", size = 20342422, upload-time = "2026-02-21T07:25:22.526Z" }, - { url = "https://files.pythonhosted.org/packages/76/44/db9035204a3363f9c0a4822c68e9a7520c13ef8d261f96b89b1375106dab/onnxruntime_migraphx-1.24.2-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:9d7f1b1a2b9651143a2080b4f42ee99eead02023de1855d1b8a02199a9c179aa", size = 20343783, upload-time = "2026-02-21T07:25:29.155Z" }, + { url = "https://files.pythonhosted.org/packages/a5/3c/8c142017df701c38c4f5d6cae0294a2891bdd0f719eaff42e87564ac1480/onnxruntime_migraphx-1.25.0-cp311-cp311-manylinux_2_34_x86_64.whl", hash = "sha256:c7f28ddb8eef13fd5e9002d5162bfe848abfd02dcf13ab6a6b8c00380c014f33", size = 21467402, upload-time = "2026-04-21T05:23:52.026Z" }, + { url = "https://files.pythonhosted.org/packages/1c/0d/21730262547fae9557cb3fc85d891d600158c92399f68e4a760aed59bffc/onnxruntime_migraphx-1.25.0-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:5c1dac156f98a9a20556bbc1e1c69d5ca1f78bad14d4953aa2b3cb2711507166", size = 21477899, upload-time = "2026-04-21T05:24:31.072Z" }, + { url = "https://files.pythonhosted.org/packages/09/b5/39764a6c87e9f97c0891f194100883192a0ce88725aba6215d499cd714b8/onnxruntime_migraphx-1.25.0-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:d778d88bfdd8818817618ff3e4d973ed861428f61ba7c8b841690c699cb29ed4", size = 21477896, upload-time = "2026-04-21T05:25:11.969Z" }, + { url = "https://files.pythonhosted.org/packages/35/97/f8d8f3141c606e14a23e52a035c123524f5b714e15a1f9425a940906dfce/onnxruntime_migraphx-1.25.0-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:5558a05f4cc1975c9974090337bbc460aae72b29ae20afb42bec93b74df0eae8", size = 21478521, upload-time = "2026-04-21T05:25:52.121Z" }, ] [[package]] @@ -1806,70 +1864,70 @@ wheels = [ [[package]] name = "orjson" -version = "3.11.8" +version = "3.11.9" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9d/1b/2024d06792d0779f9dbc51531b61c24f76c75b9f4ce05e6f3377a1814cea/orjson-3.11.8.tar.gz", hash = "sha256:96163d9cdc5a202703e9ad1b9ae757d5f0ca62f4fa0cc93d1f27b0e180cc404e", size = 5603832, upload-time = "2026-03-31T16:16:27.878Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/0c/964746fcafbd16f8ff53219ad9f6b412b34f345c75f384ad434ceaadb538/orjson-3.11.9.tar.gz", hash = "sha256:4fef17e1f8722c11587a6ef18e35902450221da0028e65dbaaa543619e68e48f", size = 5599163, upload-time = "2026-05-06T15:11:08.309Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/67/41/5aa7fa3b0f4dc6b47dcafc3cea909299c37e40e9972feabc8b6a74e2730d/orjson-3.11.8-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:003646067cc48b7fcab2ae0c562491c9b5d2cbd43f1e5f16d98fd118c5522d34", size = 229229, upload-time = "2026-03-31T16:14:50.424Z" }, - { url = "https://files.pythonhosted.org/packages/0a/d7/57e7f2458e0a2c41694f39fc830030a13053a84f837a5b73423dca1f0938/orjson-3.11.8-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:ed193ce51d77a3830cad399a529cd4ef029968761f43ddc549e1bc62b40d88f8", size = 128871, upload-time = "2026-03-31T16:14:51.888Z" }, - { url = "https://files.pythonhosted.org/packages/53/4a/e0fdb9430983e6c46e0299559275025075568aad5d21dd606faee3703924/orjson-3.11.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f30491bc4f862aa15744b9738517454f1e46e56c972a2be87d70d727d5b2a8f8", size = 132104, upload-time = "2026-03-31T16:14:53.142Z" }, - { url = "https://files.pythonhosted.org/packages/08/4a/2025a60ff3f5c8522060cda46612d9b1efa653de66ed2908591d8d82f22d/orjson-3.11.8-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6eda5b8b6be91d3f26efb7dc6e5e68ee805bc5617f65a328587b35255f138bf4", size = 130483, upload-time = "2026-03-31T16:14:54.605Z" }, - { url = "https://files.pythonhosted.org/packages/2d/3c/b9cde05bdc7b2385c66014e0620627da638d3d04e4954416ab48c31196c5/orjson-3.11.8-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee8db7bfb6fe03581bbab54d7c4124a6dd6a7f4273a38f7267197890f094675f", size = 135481, upload-time = "2026-03-31T16:14:55.901Z" }, - { url = "https://files.pythonhosted.org/packages/ff/f2/a8238e7734de7cb589fed319857a8025d509c89dc52fdcc88f39c6d03d5a/orjson-3.11.8-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d8b5231de76c528a46b57010bbd83fb51e056aa0220a372fd5065e978406f1c", size = 146819, upload-time = "2026-03-31T16:14:57.548Z" }, - { url = "https://files.pythonhosted.org/packages/db/10/dbf1e2a3cafea673b1b4350e371877b759060d6018a998643b7040e5de48/orjson-3.11.8-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:58a4a208a6fbfdb7a7327b8f201c6014f189f721fd55d047cafc4157af1bc62a", size = 132846, upload-time = "2026-03-31T16:14:58.91Z" }, - { url = "https://files.pythonhosted.org/packages/f8/fc/55e667ec9c85694038fcff00573d221b085d50777368ee3d77f38668bf3c/orjson-3.11.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f8952d6d2505c003e8f0224ff7858d341fa4e33fef82b91c4ff0ef070f2393c", size = 133580, upload-time = "2026-03-31T16:15:00.519Z" }, - { url = "https://files.pythonhosted.org/packages/7e/a6/c08c589a9aad0cb46c4831d17de212a2b6901f9d976814321ff8e69e8785/orjson-3.11.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0022bb50f90da04b009ce32c512dc1885910daa7cb10b7b0cba4505b16db82a8", size = 142042, upload-time = "2026-03-31T16:15:01.906Z" }, - { url = "https://files.pythonhosted.org/packages/5c/cc/2f78ea241d52b717d2efc38878615fe80425bf2beb6e68c984dde257a766/orjson-3.11.8-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:ff51f9d657d1afb6f410cb435792ce4e1fe427aab23d2fcd727a2876e21d4cb6", size = 423845, upload-time = "2026-03-31T16:15:03.703Z" }, - { url = "https://files.pythonhosted.org/packages/70/07/c17dcf05dd8045457538428a983bf1f1127928df5bf328cb24d2b7cddacb/orjson-3.11.8-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6dbe9a97bdb4d8d9d5367b52a7c32549bba70b2739c58ef74a6964a6d05ae054", size = 147729, upload-time = "2026-03-31T16:15:05.203Z" }, - { url = "https://files.pythonhosted.org/packages/90/6c/0fb6e8a24e682e0958d71711ae6f39110e4b9cd8cab1357e2a89cb8e1951/orjson-3.11.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a5c370674ebabe16c6ccac33ff80c62bf8a6e59439f5e9d40c1f5ab8fd2215b7", size = 136425, upload-time = "2026-03-31T16:15:07.052Z" }, - { url = "https://files.pythonhosted.org/packages/b2/35/4d3cc3a3d616035beb51b24a09bb872942dc452cf2df0c1d11ab35046d9f/orjson-3.11.8-cp311-cp311-win32.whl", hash = "sha256:0e32f7154299f42ae66f13488963269e5eccb8d588a65bc839ed986919fc9fac", size = 131870, upload-time = "2026-03-31T16:15:08.678Z" }, - { url = "https://files.pythonhosted.org/packages/13/26/9fe70f81d16b702f8c3a775e8731b50ad91d22dacd14c7599b60a0941cd1/orjson-3.11.8-cp311-cp311-win_amd64.whl", hash = "sha256:25e0c672a2e32348d2eb33057b41e754091f2835f87222e4675b796b92264f06", size = 127440, upload-time = "2026-03-31T16:15:09.994Z" }, - { url = "https://files.pythonhosted.org/packages/e8/c6/b038339f4145efd2859c1ca53097a52c0bb9cbdd24f947ebe146da1ad067/orjson-3.11.8-cp311-cp311-win_arm64.whl", hash = "sha256:9185589c1f2a944c17e26c9925dcdbc2df061cc4a145395c57f0c51f9b5dbfcd", size = 127399, upload-time = "2026-03-31T16:15:11.412Z" }, - { url = "https://files.pythonhosted.org/packages/01/f6/8d58b32ab32d9215973a1688aebd098252ee8af1766c0e4e36e7831f0295/orjson-3.11.8-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:1cd0b77e77c95758f8e1100139844e99f3ccc87e71e6fc8e1c027e55807c549f", size = 229233, upload-time = "2026-03-31T16:15:12.762Z" }, - { url = "https://files.pythonhosted.org/packages/a9/8b/2ffe35e71f6b92622e8ea4607bf33ecf7dfb51b3619dcfabfd36cbe2d0a5/orjson-3.11.8-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:6a3d159d5ffa0e3961f353c4b036540996bf8b9697ccc38261c0eac1fd3347a6", size = 128772, upload-time = "2026-03-31T16:15:14.237Z" }, - { url = "https://files.pythonhosted.org/packages/27/d2/1f8682ae50d5c6897a563cb96bc106da8c9cb5b7b6e81a52e4cc086679b9/orjson-3.11.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76070a76e9c5ae661e2d9848f216980d8d533e0f8143e6ed462807b242e3c5e8", size = 131946, upload-time = "2026-03-31T16:15:15.607Z" }, - { url = "https://files.pythonhosted.org/packages/52/4b/5500f76f0eece84226e0689cb48dcde081104c2fa6e2483d17ca13685ffb/orjson-3.11.8-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:54153d21520a71a4c82a0dbb4523e468941d549d221dc173de0f019678cf3813", size = 130368, upload-time = "2026-03-31T16:15:17.066Z" }, - { url = "https://files.pythonhosted.org/packages/da/4e/58b927e08fbe9840e6c920d9e299b051ea667463b1f39a56e668669f8508/orjson-3.11.8-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:469ac2125611b7c5741a0b3798cd9e5786cbad6345f9f400c77212be89563bec", size = 135540, upload-time = "2026-03-31T16:15:18.404Z" }, - { url = "https://files.pythonhosted.org/packages/56/7c/ba7cb871cba1bcd5cd02ee34f98d894c6cea96353ad87466e5aef2429c60/orjson-3.11.8-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:14778ffd0f6896aa613951a7fbf4690229aa7a543cb2bfbe9f358e08aafa9546", size = 146877, upload-time = "2026-03-31T16:15:19.833Z" }, - { url = "https://files.pythonhosted.org/packages/0b/5d/eb9c25fc1386696c6a342cd361c306452c75e0b55e86ad602dd4827a7fd7/orjson-3.11.8-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea56a955056a6d6c550cf18b3348656a9d9a4f02e2d0c02cabf3c73f1055d506", size = 132837, upload-time = "2026-03-31T16:15:21.282Z" }, - { url = "https://files.pythonhosted.org/packages/37/87/5ddeb7fc1fbd9004aeccab08426f34c81a5b4c25c7061281862b015fce2b/orjson-3.11.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53a0f57e59a530d18a142f4d4ba6dfc708dc5fdedce45e98ff06b44930a2a48f", size = 133624, upload-time = "2026-03-31T16:15:22.641Z" }, - { url = "https://files.pythonhosted.org/packages/22/09/90048793db94ee4b2fcec4ac8e5ddb077367637d6650be896b3494b79bb7/orjson-3.11.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9b48e274f8824567d74e2158199e269597edf00823a1b12b63d48462bbf5123e", size = 141904, upload-time = "2026-03-31T16:15:24.435Z" }, - { url = "https://files.pythonhosted.org/packages/c0/cf/eb284847487821a5d415e54149a6449ba9bfc5872ce63ab7be41b8ec401c/orjson-3.11.8-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:3f262401086a3960586af06c054609365e98407151f5ea24a62893a40d80dbbb", size = 423742, upload-time = "2026-03-31T16:15:26.155Z" }, - { url = "https://files.pythonhosted.org/packages/44/09/e12423d327071c851c13e76936f144a96adacfc037394dec35ac3fc8d1e8/orjson-3.11.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8e8c6218b614badf8e229b697865df4301afa74b791b6c9ade01d19a9953a942", size = 147806, upload-time = "2026-03-31T16:15:27.909Z" }, - { url = "https://files.pythonhosted.org/packages/b3/6d/37c2589ba864e582ffe7611643314785c6afb1f83c701654ef05daa8fcc7/orjson-3.11.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:093d489fa039ddade2db541097dbb484999fcc65fc2b0ff9819141e2ab364f25", size = 136485, upload-time = "2026-03-31T16:15:29.749Z" }, - { url = "https://files.pythonhosted.org/packages/be/c9/135194a02ab76b04ed9a10f68624b7ebd238bbe55548878b11ff15a0f352/orjson-3.11.8-cp312-cp312-win32.whl", hash = "sha256:e0950ed1bcb9893f4293fd5c5a7ee10934fbf82c4101c70be360db23ce24b7d2", size = 131966, upload-time = "2026-03-31T16:15:31.687Z" }, - { url = "https://files.pythonhosted.org/packages/ed/9a/9796f8fbe3cf30ce9cb696748dbb535e5c87be4bf4fe2e9ca498ef1fa8cf/orjson-3.11.8-cp312-cp312-win_amd64.whl", hash = "sha256:3cf17c141617b88ced4536b2135c552490f07799f6ad565948ea07bef0dcb9a6", size = 127441, upload-time = "2026-03-31T16:15:33.333Z" }, - { url = "https://files.pythonhosted.org/packages/cc/47/5aaf54524a7a4a0dd09dd778f3fa65dd2108290615b652e23d944152bc8e/orjson-3.11.8-cp312-cp312-win_arm64.whl", hash = "sha256:48854463b0572cc87dac7d981aa72ed8bf6deedc0511853dc76b8bbd5482d36d", size = 127364, upload-time = "2026-03-31T16:15:34.748Z" }, - { url = "https://files.pythonhosted.org/packages/66/7f/95fba509bb2305fab0073558f1e8c3a2ec4b2afe58ed9fcb7d3b8beafe94/orjson-3.11.8-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:3f23426851d98478c8970da5991f84784a76682213cd50eb73a1da56b95239dc", size = 229180, upload-time = "2026-03-31T16:15:36.426Z" }, - { url = "https://files.pythonhosted.org/packages/f6/9d/b237215c743ca073697d759b5503abd2cb8a0d7b9c9e21f524bcf176ab66/orjson-3.11.8-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:ebaed4cef74a045b83e23537b52ef19a367c7e3f536751e355a2a394f8648559", size = 128754, upload-time = "2026-03-31T16:15:38.049Z" }, - { url = "https://files.pythonhosted.org/packages/42/3d/27d65b6d11e63f133781425f132807aef793ed25075fec686fc8e46dd528/orjson-3.11.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97c8f5d3b62380b70c36ffacb2a356b7c6becec86099b177f73851ba095ef623", size = 131877, upload-time = "2026-03-31T16:15:39.484Z" }, - { url = "https://files.pythonhosted.org/packages/dd/cc/faee30cd8f00421999e40ef0eba7332e3a625ce91a58200a2f52c7fef235/orjson-3.11.8-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:436c4922968a619fb7fef1ccd4b8b3a76c13b67d607073914d675026e911a65c", size = 130361, upload-time = "2026-03-31T16:15:41.274Z" }, - { url = "https://files.pythonhosted.org/packages/5c/bb/a6c55896197f97b6d4b4e7c7fd77e7235517c34f5d6ad5aadd43c54c6d7c/orjson-3.11.8-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1ab359aff0436d80bfe8a23b46b5fea69f1e18aaf1760a709b4787f1318b317f", size = 135521, upload-time = "2026-03-31T16:15:42.758Z" }, - { url = "https://files.pythonhosted.org/packages/9c/7c/ca3a3525aa32ff636ebb1778e77e3587b016ab2edb1b618b36ba96f8f2c0/orjson-3.11.8-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f89b6d0b3a8d81e1929d3ab3d92bbc225688bd80a770c49432543928fe09ac55", size = 146862, upload-time = "2026-03-31T16:15:44.341Z" }, - { url = "https://files.pythonhosted.org/packages/3c/0c/18a9d7f18b5edd37344d1fd5be17e94dc652c67826ab749c6e5948a78112/orjson-3.11.8-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:29c009e7a2ca9ad0ed1376ce20dd692146a5d9fe4310848904b6b4fee5c5c137", size = 132847, upload-time = "2026-03-31T16:15:46.368Z" }, - { url = "https://files.pythonhosted.org/packages/23/91/7e722f352ad67ca573cee44de2a58fb810d0f4eb4e33276c6a557979fd8a/orjson-3.11.8-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:705b895b781b3e395c067129d8551655642dfe9437273211d5404e87ac752b53", size = 133637, upload-time = "2026-03-31T16:15:48.123Z" }, - { url = "https://files.pythonhosted.org/packages/af/04/32845ce13ac5bd1046ddb02ac9432ba856cc35f6d74dde95864fe0ad5523/orjson-3.11.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:88006eda83858a9fdf73985ce3804e885c2befb2f506c9a3723cdeb5a2880e3e", size = 141906, upload-time = "2026-03-31T16:15:49.626Z" }, - { url = "https://files.pythonhosted.org/packages/02/5e/c551387ddf2d7106d9039369862245c85738b828844d13b99ccb8d61fd06/orjson-3.11.8-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:55120759e61309af7fcf9e961c6f6af3dde5921cdb3ee863ef63fd9db126cae6", size = 423722, upload-time = "2026-03-31T16:15:51.176Z" }, - { url = "https://files.pythonhosted.org/packages/00/a3/ecfe62434096f8a794d4976728cb59bcfc4a643977f21c2040545d37eb4c/orjson-3.11.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:98bdc6cb889d19bed01de46e67574a2eab61f5cc6b768ed50e8ac68e9d6ffab6", size = 147801, upload-time = "2026-03-31T16:15:52.939Z" }, - { url = "https://files.pythonhosted.org/packages/18/6d/0dce10b9f6643fdc59d99333871a38fa5a769d8e2fc34a18e5d2bfdee900/orjson-3.11.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:708c95f925a43ab9f34625e45dcdadf09ec8a6e7b664a938f2f8d5650f6c090b", size = 136460, upload-time = "2026-03-31T16:15:54.431Z" }, - { url = "https://files.pythonhosted.org/packages/01/d6/6dde4f31842d87099238f1f07b459d24edc1a774d20687187443ab044191/orjson-3.11.8-cp313-cp313-win32.whl", hash = "sha256:01c4e5a6695dc09098f2e6468a251bc4671c50922d4d745aff1a0a33a0cf5b8d", size = 131956, upload-time = "2026-03-31T16:15:56.081Z" }, - { url = "https://files.pythonhosted.org/packages/c1/f9/4e494a56e013db957fb77186b818b916d4695b8fa2aa612364974160e91b/orjson-3.11.8-cp313-cp313-win_amd64.whl", hash = "sha256:c154a35dd1330707450bb4d4e7dd1f17fa6f42267a40c1e8a1daa5e13719b4b8", size = 127410, upload-time = "2026-03-31T16:15:57.54Z" }, - { url = "https://files.pythonhosted.org/packages/57/7f/803203d00d6edb6e9e7eef421d4e1adbb5ea973e40b3533f3cfd9aeb374e/orjson-3.11.8-cp313-cp313-win_arm64.whl", hash = "sha256:4861bde57f4d253ab041e374f44023460e60e71efaa121f3c5f0ed457c3a701e", size = 127338, upload-time = "2026-03-31T16:15:59.106Z" }, - { url = "https://files.pythonhosted.org/packages/6d/35/b01910c3d6b85dc882442afe5060cbf719c7d1fc85749294beda23d17873/orjson-3.11.8-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:ec795530a73c269a55130498842aaa762e4a939f6ce481a7e986eeaa790e9da4", size = 229171, upload-time = "2026-03-31T16:16:00.651Z" }, - { url = "https://files.pythonhosted.org/packages/c2/56/c9ec97bd11240abef39b9e5d99a15462809c45f677420fd148a6c5e6295e/orjson-3.11.8-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:c492a0e011c0f9066e9ceaa896fbc5b068c54d365fea5f3444b697ee01bc8625", size = 128746, upload-time = "2026-03-31T16:16:02.673Z" }, - { url = "https://files.pythonhosted.org/packages/3b/e4/66d4f30a90de45e2f0cbd9623588e8ae71eef7679dbe2ae954ed6d66a41f/orjson-3.11.8-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:883206d55b1bd5f5679ad5e6ddd3d1a5e3cac5190482927fdb8c78fb699193b5", size = 131867, upload-time = "2026-03-31T16:16:04.342Z" }, - { url = "https://files.pythonhosted.org/packages/19/30/2a645fc9286b928675e43fa2a3a16fb7b6764aa78cc719dc82141e00f30b/orjson-3.11.8-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5774c1fdcc98b2259800b683b19599c133baeb11d60033e2095fd9d4667b82db", size = 124664, upload-time = "2026-03-31T16:16:05.837Z" }, - { url = "https://files.pythonhosted.org/packages/db/44/77b9a86d84a28d52ba3316d77737f6514e17118119ade3f91b639e859029/orjson-3.11.8-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ac7381c83dd3d4a6347e6635950aa448f54e7b8406a27c7ecb4a37e9f1ae08b", size = 129701, upload-time = "2026-03-31T16:16:07.407Z" }, - { url = "https://files.pythonhosted.org/packages/b3/ea/eff3d9bfe47e9bc6969c9181c58d9f71237f923f9c86a2d2f490cd898c82/orjson-3.11.8-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:14439063aebcb92401c11afc68ee4e407258d2752e62d748b6942dad20d2a70d", size = 141202, upload-time = "2026-03-31T16:16:09.48Z" }, - { url = "https://files.pythonhosted.org/packages/52/c8/90d4b4c60c84d62068d0cf9e4d8f0a4e05e76971d133ac0c60d818d4db20/orjson-3.11.8-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fa72e71977bff96567b0f500fc5bfd2fdf915f34052c782a4c6ebbdaa97aa858", size = 127194, upload-time = "2026-03-31T16:16:11.02Z" }, - { url = "https://files.pythonhosted.org/packages/8d/c7/ea9e08d1f0ba981adffb629811148b44774d935171e7b3d780ae43c4c254/orjson-3.11.8-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7679bc2f01bb0d219758f1a5f87bb7c8a81c0a186824a393b366876b4948e14f", size = 133639, upload-time = "2026-03-31T16:16:13.434Z" }, - { url = "https://files.pythonhosted.org/packages/6c/8c/ddbbfd6ba59453c8fc7fe1d0e5983895864e264c37481b2a791db635f046/orjson-3.11.8-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:14f7b8fcb35ef403b42fa5ecfa4ed032332a91f3dc7368fbce4184d59e1eae0d", size = 141914, upload-time = "2026-03-31T16:16:14.955Z" }, - { url = "https://files.pythonhosted.org/packages/4e/31/dbfbefec9df060d34ef4962cd0afcb6fa7a9ec65884cb78f04a7859526c3/orjson-3.11.8-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:c2bdf7b2facc80b5e34f48a2d557727d5c5c57a8a450de122ae81fa26a81c1bc", size = 423800, upload-time = "2026-03-31T16:16:16.594Z" }, - { url = "https://files.pythonhosted.org/packages/87/cf/f74e9ae9803d4ab46b163494adba636c6d7ea955af5cc23b8aaa94cfd528/orjson-3.11.8-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ccd7ba1b0605813a0715171d39ec4c314cb97a9c85893c2c5c0c3a3729df38bf", size = 147837, upload-time = "2026-03-31T16:16:18.585Z" }, - { url = "https://files.pythonhosted.org/packages/64/e6/9214f017b5db85e84e68602792f742e5dc5249e963503d1b356bee611e01/orjson-3.11.8-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cdbc8c9c02463fef4d3c53a9ba3336d05496ec8e1f1c53326a1e4acc11f5c600", size = 136441, upload-time = "2026-03-31T16:16:20.151Z" }, - { url = "https://files.pythonhosted.org/packages/24/dd/3590348818f58f837a75fb969b04cdf187ae197e14d60b5e5a794a38b79d/orjson-3.11.8-cp314-cp314-win32.whl", hash = "sha256:0b57f67710a8cd459e4e54eb96d5f77f3624eba0c661ba19a525807e42eccade", size = 131983, upload-time = "2026-03-31T16:16:21.823Z" }, - { url = "https://files.pythonhosted.org/packages/3f/0f/b6cb692116e05d058f31ceee819c70f097fa9167c82f67fabe7516289abc/orjson-3.11.8-cp314-cp314-win_amd64.whl", hash = "sha256:735e2262363dcbe05c35e3a8869898022af78f89dde9e256924dc02e99fe69ca", size = 127396, upload-time = "2026-03-31T16:16:23.685Z" }, - { url = "https://files.pythonhosted.org/packages/c0/d1/facb5b5051fabb0ef9d26c6544d87ef19a939a9a001198655d0d891062dd/orjson-3.11.8-cp314-cp314-win_arm64.whl", hash = "sha256:6ccdea2c213cf9f3d9490cbd5d427693c870753df41e6cb375bd79bcbafc8817", size = 127330, upload-time = "2026-03-31T16:16:25.496Z" }, + { url = "https://files.pythonhosted.org/packages/1e/51/3fb9e65ae76ee97bd611869a503fa3fc0a6e81dd8b737cf3003f682df7ff/orjson-3.11.9-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:f01c4818b3fc9b0da8e096722a84318071eaa118df35f6ed2344da0e73a5444f", size = 228522, upload-time = "2026-05-06T15:09:35.362Z" }, + { url = "https://files.pythonhosted.org/packages/16/fa/9d54b07cb3f3b0bfd57841478e42d7a0ece4a9f49f9907eecf5a45461687/orjson-3.11.9-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:3ebca4179031ee716ed076ffadc29428e900512f6fccee8614c9983157fcf19c", size = 128463, upload-time = "2026-05-06T15:09:37.063Z" }, + { url = "https://files.pythonhosted.org/packages/88/b1/6ceafc2eefd0a553e3be77ce6c49d107e772485d9568629376171c50e634/orjson-3.11.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48ee05097750de0ff69ed5b7bbcf0732182fd57a24043dcc2a1da780a5ead3a5", size = 132306, upload-time = "2026-05-06T15:09:38.299Z" }, + { url = "https://files.pythonhosted.org/packages/ea/76/f11311285324a40aab1e3031385c50b635a7cd0734fdaf60c7e89a696f60/orjson-3.11.9-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a6082706765a95a6680d812e1daf1c0cfe8adec7831b3ff3b625693f3b461b1c", size = 127988, upload-time = "2026-05-06T15:09:39.597Z" }, + { url = "https://files.pythonhosted.org/packages/9e/85/0ef63bcf1337f44031ce9b91b1919563f62a37527b3ea4368bb15a22e5d7/orjson-3.11.9-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:277fefe9d76ee17eb14debf399e3533d4d63b5f677a4d3719eb763536af1f4bd", size = 135188, upload-time = "2026-05-06T15:09:40.957Z" }, + { url = "https://files.pythonhosted.org/packages/05/94/b0d27090ea8a2095db3c2bd1b1c96f96f19bbb494d7fef33130e846e613d/orjson-3.11.9-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:03db380e3780fa0015ed776a90f20e8e20bb11dde13b216ce19e5718e3dfba62", size = 145937, upload-time = "2026-05-06T15:09:42.249Z" }, + { url = "https://files.pythonhosted.org/packages/09/eb/75d50c29c05b8054013e221e598820a365c8e64065312e75e202ed880709/orjson-3.11.9-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33d7d766701847dc6729846362dc27895d2f2d2251264f9d10e7cb9878194877", size = 132758, upload-time = "2026-05-06T15:09:43.945Z" }, + { url = "https://files.pythonhosted.org/packages/49/bd/360686f39348aa88827cb6fbf7dc606fd41c831a35235e1abf1db8e3a9e6/orjson-3.11.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:147302878da387104b66bb4a8b0227d1d487e976ce41a8501916161072ed87b1", size = 133971, upload-time = "2026-05-06T15:09:45.239Z" }, + { url = "https://files.pythonhosted.org/packages/0e/30/3178eb16f3221aeef068b6f1f1ebe05f656ea5c6dffe9f6c917329fe17a3/orjson-3.11.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3513550321f8c8c811a7c3297b8a630e82dc08e4c10216d07703c997776236cd", size = 141685, upload-time = "2026-05-06T15:09:46.858Z" }, + { url = "https://files.pythonhosted.org/packages/5f/f1/ff2f19ed0225f9680fafa42febca3570dd59444ebf190980738d376214c2/orjson-3.11.9-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:c5d001196b89fa9cf0a4ab79766cd835b991a166e4b621ba95089edc50c429ff", size = 415167, upload-time = "2026-05-06T15:09:48.312Z" }, + { url = "https://files.pythonhosted.org/packages/9b/61/863bddf0da6e9e586765414debd54b4e58db05f560902b6d00658cb88636/orjson-3.11.9-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:16969c9d369c98eb084889c6e4d2d39b77c7eb38ceccf8da2a9fff62ae908980", size = 147913, upload-time = "2026-05-06T15:09:49.733Z" }, + { url = "https://files.pythonhosted.org/packages/b6/8a/4081492586d75b073d60c5271a8d0f05a0955cabf1e34c8473f6fcd84235/orjson-3.11.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:63e0efbc991250c0b3143488fa57d95affcabbfc63c99c48d625dd37779aafe2", size = 136959, upload-time = "2026-05-06T15:09:51.311Z" }, + { url = "https://files.pythonhosted.org/packages/0d/bd/70b6ab193594d7abb875320c0a7c8335e846f28968c432c31042409c3c8d/orjson-3.11.9-cp311-cp311-win32.whl", hash = "sha256:14ed654580c1ed2bc217352ec82f91b047aef82951aa71c7f64e0dcb03c0e180", size = 131533, upload-time = "2026-05-06T15:09:52.637Z" }, + { url = "https://files.pythonhosted.org/packages/3f/17/1a1a228183d62d1b77e2c30d210f47dd4768b310ebe1607c63e3c0e3a71e/orjson-3.11.9-cp311-cp311-win_amd64.whl", hash = "sha256:57ea77fb70a448ce87d18fca050193202a3da5e54598f6501ca5476fb66cfe02", size = 127106, upload-time = "2026-05-06T15:09:54.204Z" }, + { url = "https://files.pythonhosted.org/packages/b8/95/285de5fa296d09681ee9c546cd4a8aeb773b701cf343dc125994f4d52953/orjson-3.11.9-cp311-cp311-win_arm64.whl", hash = "sha256:19b72ed11572a2ee51a67a903afbe5af504f84ed6f529c0fe44b0ab3fb5cc697", size = 126848, upload-time = "2026-05-06T15:09:55.551Z" }, + { url = "https://files.pythonhosted.org/packages/16/6d/11867a3ffa3a3608d84a4de51ef4dd0896d6b5cc9132fbe1daf593e677bc/orjson-3.11.9-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9ef6fe90aadef185c7b128859f40beb24720b4ecea95379fc9000931179c3a49", size = 228515, upload-time = "2026-05-06T15:09:57.265Z" }, + { url = "https://files.pythonhosted.org/packages/24/75/05912954c8b288f34fcf5cd4b9b071cb4f6e77b9961e175e56ebb258089f/orjson-3.11.9-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:e5c9b8f28e726e97d97696c826bc7bea5d71cecd63576dba92924a32c1961291", size = 128409, upload-time = "2026-05-06T15:09:59.063Z" }, + { url = "https://files.pythonhosted.org/packages/ab/86/1c3a47df3bc8191ea9ac51603bbb872a95167a364320c269f2557911f406/orjson-3.11.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26a473dbb4162108b27901492546f83c76fdcea3d0eadff00ae7a07e18dcce09", size = 132106, upload-time = "2026-05-06T15:10:00.798Z" }, + { url = "https://files.pythonhosted.org/packages/d7/cf/b33b5f3e695ae7d63feef9d915c37cc3b8f465493dcd4f8e0b4c697a2366/orjson-3.11.9-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:011382e2a60fda9d46f1cdee31068cfc52ffe952b587d683ec0463002802a0f4", size = 127864, upload-time = "2026-05-06T15:10:02.15Z" }, + { url = "https://files.pythonhosted.org/packages/31/6a/6cf69385a58208024fcb8c014e2141b8ce838aba6492b589f8acfff97fab/orjson-3.11.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c2d3dc759490128c5c1711a53eeaa8ee1d437fd0038ffd2b6008abf46db3f882", size = 135213, upload-time = "2026-05-06T15:10:03.515Z" }, + { url = "https://files.pythonhosted.org/packages/e8/f8/0b1bd3e8f2efcdd376af5c8cfd79eaf13f018080c0089c80ebd724e3c7fb/orjson-3.11.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d8ea516b3726d190e1b4297e6f4e7a8650347ae053868a18163b4dd3641d1fff", size = 145994, upload-time = "2026-05-06T15:10:05.083Z" }, + { url = "https://files.pythonhosted.org/packages/f3/59/dab79f61044c529d2c81aecdc589b1f833a1c8dec11ba3b1c2498a02ca7e/orjson-3.11.9-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:380cdce7ba24989af81d0a7013d0aaec5d0e2a21734c0e2681b1bc4f141957fe", size = 132744, upload-time = "2026-05-06T15:10:06.853Z" }, + { url = "https://files.pythonhosted.org/packages/0e/a4/82b7a2fe5d8a67a59ed831b24d59a3d46ea7d207b66e1602d376541d94a6/orjson-3.11.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be4fa4f0af7fa18951f7ab3fc2148e223af211bf03f59e1c6034ec3f97f21d61", size = 134014, upload-time = "2026-05-06T15:10:08.213Z" }, + { url = "https://files.pythonhosted.org/packages/50/c7/375e83a76851b73b2e39f3bcf0e5a19e2b89bad13e5bca97d0b293d27f24/orjson-3.11.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a8f5f8bc7ce7d59f08d9f99fa510c06496164a24cb5f3d34537dbd9ca30132e2", size = 141509, upload-time = "2026-05-06T15:10:09.595Z" }, + { url = "https://files.pythonhosted.org/packages/7f/7c/49d5d82a3d3097f641f094f552131f1e2723b0b8cb0fa2874ab65ecfffa6/orjson-3.11.9-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:4d7fde5501b944f83b3e665e1b31343ff6e154b15560a16b7130ea1e594a4206", size = 415127, upload-time = "2026-05-06T15:10:11.049Z" }, + { url = "https://files.pythonhosted.org/packages/3a/dc/7446c538590d55f455647e5f3c61fc33f7108714e7afcffa6a2a033f8350/orjson-3.11.9-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cde1a448023ba7d5bb4c01c5afb48894380b5e4956e0627266526587ef4e535f", size = 148025, upload-time = "2026-05-06T15:10:12.842Z" }, + { url = "https://files.pythonhosted.org/packages/df/e5/4d2d8af06f788329b4f78f8cc3679bb395392fcaa1e4d8d3c33e85308fa4/orjson-3.11.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:71e63adb0e1f1ed5d9e168f50a91ceb93ae6420731d222dc7da5c69409aa47aa", size = 136943, upload-time = "2026-05-06T15:10:14.405Z" }, + { url = "https://files.pythonhosted.org/packages/06/69/850264ccf6d80f6b174620d30a87f65c9b1490aba33fe6b62798e618cad3/orjson-3.11.9-cp312-cp312-win32.whl", hash = "sha256:2d057a602cdd19a0ad680417527c45b6961a095081c0f46fe0e03e304aac6470", size = 131606, upload-time = "2026-05-06T15:10:15.791Z" }, + { url = "https://files.pythonhosted.org/packages/b9/d5/973a43fc9c55e20f2051e9830997649f669be0cb3ca52192087c0143f118/orjson-3.11.9-cp312-cp312-win_amd64.whl", hash = "sha256:59e403b1cc5a676da8eaf31f6254801b7341b3e29efa85f92b48d272637e77be", size = 127101, upload-time = "2026-05-06T15:10:17.129Z" }, + { url = "https://files.pythonhosted.org/packages/fe/ae/495470f0e4a18f73fa10b7f6b84b464ec4cc5291c4e0c7c2a6c400bef006/orjson-3.11.9-cp312-cp312-win_arm64.whl", hash = "sha256:9af678d6488357948f1f84c6cd1c1d397c014e1ae2f98ae082a44eb48f602624", size = 126736, upload-time = "2026-05-06T15:10:18.645Z" }, + { url = "https://files.pythonhosted.org/packages/32/33/93fcc25907235c344ae73122f8a4e01d2d393ef062b4af7d2e2487a32c37/orjson-3.11.9-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:4bab1b2d6141fe7b32ae71dac905666ece4f94936efbfb13d55bb7739a3a6021", size = 228458, upload-time = "2026-05-06T15:10:20.079Z" }, + { url = "https://files.pythonhosted.org/packages/8f/27/b1e6dadb3c080313c03fdd8067b85e6a0460c7d8d6a1c3984ef77b904e4d/orjson-3.11.9-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:844417969855fc7a41be124aafe83dc424592a7f77cd4501900c67307122b92c", size = 128368, upload-time = "2026-05-06T15:10:21.549Z" }, + { url = "https://files.pythonhosted.org/packages/21/0f/c9ede0bf052f6b4051e64a7d4fa91b725cccf8321a6a786e86eb03519f00/orjson-3.11.9-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffe02797b5e9f3a9d8292ddcd289b474ad13e81ad83cd1891a240811f1d2cb81", size = 132070, upload-time = "2026-05-06T15:10:23.371Z" }, + { url = "https://files.pythonhosted.org/packages/fd/26/d398e28048dc18205bbe812f2c88cb9b40313db2470778e25964796458fe/orjson-3.11.9-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e4eed3b200023042814d2fc8a5d2e880f13b52e1ed2485e83da4f3962f7dc1a", size = 127892, upload-time = "2026-05-06T15:10:24.714Z" }, + { url = "https://files.pythonhosted.org/packages/66/60/52b0054c4c700d5aa7fc5b7ca96917400d8f061307778578e67a10e25852/orjson-3.11.9-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8aff7da9952a5ad1cef8e68017724d96c7b9a66e99e91d6252e1b133d67a7b10", size = 135217, upload-time = "2026-05-06T15:10:26.084Z" }, + { url = "https://files.pythonhosted.org/packages/d5/97/1e3dc2b2a28b7b2528f403d2fc1d79ec5f39af3bc143ab65d3ec26426385/orjson-3.11.9-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4d4e98d6f3b8afed8bc8cd9718ec0cdf46661826beefb53fe8eafb37f2bf0362", size = 145980, upload-time = "2026-05-06T15:10:28.062Z" }, + { url = "https://files.pythonhosted.org/packages/fc/39/31fbfe7850f2de32dee7e7e5c09f26d403ab01e440ac96001c6b01ad3c99/orjson-3.11.9-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a81d52442a7c99b3662333235b3adf96a1715864658b35bb797212be7bddb97", size = 132738, upload-time = "2026-05-06T15:10:29.727Z" }, + { url = "https://files.pythonhosted.org/packages/a1/08/dca0082dd2a194acb93e5457e73455388e2e2ca464a2672449a9ddbb679d/orjson-3.11.9-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e39364e726a8fff737309aff059ff67d8a8c8d5b677be7bb49a8b3e84b7e218", size = 134033, upload-time = "2026-05-06T15:10:31.152Z" }, + { url = "https://files.pythonhosted.org/packages/11/d4/5bdb0626801230139987385554c5d4c42255218ac906525bf4347f22cd95/orjson-3.11.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4fd66214623f1b17501df9f0543bef0b833979ab5b6ded1e1d123222866aa8c9", size = 141492, upload-time = "2026-05-06T15:10:32.641Z" }, + { url = "https://files.pythonhosted.org/packages/fa/88/a21fb53b3ede6703aede6dce4710ed4111e5b201cfa6bbff5e544f9d47d7/orjson-3.11.9-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:8ecc30f10465fa1e0ce13fd01d9e22c316e5053a719a8d915d4545a09a5ff677", size = 415087, upload-time = "2026-05-06T15:10:34.438Z" }, + { url = "https://files.pythonhosted.org/packages/3d/57/1b30daf70f0d8180e9a73cefbfbdd99e4bf19eb020466502b01fba7e0e50/orjson-3.11.9-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:97db4c94a7db398a5bd636273324f0b3fd58b350bbbac8bb380ceb825a9b40f4", size = 148031, upload-time = "2026-05-06T15:10:36.358Z" }, + { url = "https://files.pythonhosted.org/packages/04/83/45fbb6d962e260807f99441db9613cee868ceda4baceda59b3720a563f97/orjson-3.11.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9f78cf8fec5bd627f4082b8dfeac7871b43d7f3274904492a43dab39f18a19a0", size = 136915, upload-time = "2026-05-06T15:10:38.013Z" }, + { url = "https://files.pythonhosted.org/packages/5f/cc/2d10025f9056d376e4127ec05a5808b218d46f035fdc08178a5411b34250/orjson-3.11.9-cp313-cp313-win32.whl", hash = "sha256:d4087e5c0209a0a8efe4de3303c234b9c44d1174161dcd851e8eea07c7560b32", size = 131613, upload-time = "2026-05-06T15:10:39.569Z" }, + { url = "https://files.pythonhosted.org/packages/67/bd/2775ff28bfe883b9aa1ff348300542eb2ef1ee18d8ae0e3a49846817a865/orjson-3.11.9-cp313-cp313-win_amd64.whl", hash = "sha256:051b102c93b4f634e89f3866b07b9a9a98915ada541f4ec30f177067b2694979", size = 127086, upload-time = "2026-05-06T15:10:41.262Z" }, + { url = "https://files.pythonhosted.org/packages/91/2b/d26799e580939e32a7da9a39531bc9e58e15ca32ffaa6a8cb3e9bb0d22cd/orjson-3.11.9-cp313-cp313-win_arm64.whl", hash = "sha256:cce9127885941bd28f080cecf1f1d288336b7e0d812c345b08be88b572796254", size = 126696, upload-time = "2026-05-06T15:10:42.651Z" }, + { url = "https://files.pythonhosted.org/packages/8e/eb/5da01e356015aee6ecfa1187ced87aef51364e306f5e695dd52719bf0e78/orjson-3.11.9-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:b6ef1979adc4bc243523f1a2ba91418030a8e29b0a99cbe7e0e2d6807d4dce6e", size = 228465, upload-time = "2026-05-06T15:10:44.097Z" }, + { url = "https://files.pythonhosted.org/packages/64/62/3e0e0c14c957133bcd855395c62b55ed4e3b0af23ffea11b032cb1dcbdb1/orjson-3.11.9-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:f36b7f32c7c0db4a719f1fc5824db4a9c6f8bd1a354debb91faf26ebf3a4c71e", size = 128364, upload-time = "2026-05-06T15:10:45.839Z" }, + { url = "https://files.pythonhosted.org/packages/5a/5a/07d8aa117211a8ed7630bda80c8c0b14d04e0f8dcf99bcf49656e4a710eb/orjson-3.11.9-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08f4d8ebb44925c794e535b2bebc507cebf32209df81de22ae285fb0d8d66de0", size = 132063, upload-time = "2026-05-06T15:10:47.267Z" }, + { url = "https://files.pythonhosted.org/packages/d6/ec/4acaf21483e18aa945be74a474c74b434f284b549f275a0a39b9f98956e9/orjson-3.11.9-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6cc7923789694fd58f001cbcac7e47abc13af4d560ebbfcf3b41a8b1a0748124", size = 122356, upload-time = "2026-05-06T15:10:48.765Z" }, + { url = "https://files.pythonhosted.org/packages/13/d8/5f0555e7638801323b7a75850f92e7dfa891bc84fe27a1ba4449170d1200/orjson-3.11.9-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea5c46eb2d3af39e806b986f4b09d5c2706a1f5afde3cbf7544ce6616127173c", size = 129592, upload-time = "2026-05-06T15:10:50.13Z" }, + { url = "https://files.pythonhosted.org/packages/b6/30/ed9860412a3603ceb3c5955bfd72d28b9d0e7ba6ed81add14f83d7114236/orjson-3.11.9-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f5d89a2ed90731df3be64bab0aa44f78bff39fdc9d71c291f4a8023aa46425b7", size = 140491, upload-time = "2026-05-06T15:10:51.582Z" }, + { url = "https://files.pythonhosted.org/packages/d0/17/adc514dea7ac7c505527febf884934b815d34f0c7b8693c1a8b39c5c4a57/orjson-3.11.9-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:25e4aed0312d292c09f61af25bba34e0b2c88546041472b09088c39a4d828af1", size = 127309, upload-time = "2026-05-06T15:10:53.329Z" }, + { url = "https://files.pythonhosted.org/packages/76/3e/c0b690253f0b82d86e99949af13533363acfb5432ecb5d53dd5b3bce9c34/orjson-3.11.9-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aaea64f3f467d22e70eeed68bdccb3bc4f83f650446c4a03c59f2cba28a108db", size = 134030, upload-time = "2026-05-06T15:10:54.988Z" }, + { url = "https://files.pythonhosted.org/packages/c1/7a/bc82a0bb25e9faaf92dc4d9ef002732efc09737706af83e346788641d4a7/orjson-3.11.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a028425d1b440c5d92a6be1e1a020739dfe67ea87d96c6dbe828c1b30041728b", size = 141482, upload-time = "2026-05-06T15:10:56.663Z" }, + { url = "https://files.pythonhosted.org/packages/01/55/e69188b939f77d5d32a9833745ace31ea5ccae3ab613a1ec185d3cd2c4fb/orjson-3.11.9-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:5b192c6cf397e4455b11523c5cf2b18ed084c1bbd61b6c0926344d2129481972", size = 415178, upload-time = "2026-05-06T15:10:58.446Z" }, + { url = "https://files.pythonhosted.org/packages/2e/1a/b8a5a7ac527e80b9cb11d51e3f6689b709279183264b9ec5c7bc680bb8b5/orjson-3.11.9-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ea407d4ccf5891d667d045fecae97a7a1e5e87b3b97f97ae1803c2e741130be0", size = 148089, upload-time = "2026-05-06T15:11:00.441Z" }, + { url = "https://files.pythonhosted.org/packages/97/4e/00503f64204bf859b37213a63927028f30fb6268cd8677fb0a5ad48155e1/orjson-3.11.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5f63aaf97afd9f6dec5b1a68e1b8da12bfccb4cb9a9a65c3e0b6c847849e7586", size = 136921, upload-time = "2026-05-06T15:11:02.176Z" }, + { url = "https://files.pythonhosted.org/packages/0d/ba/a23b82a0a8d0ed7bed4e5f5035aae751cad4ff6a1e8d2ecd14d8860f5929/orjson-3.11.9-cp314-cp314-win32.whl", hash = "sha256:e30ab17845bb9fa54ccf67fa4f9f5282652d54faa6d17452f47d0f369d038673", size = 131638, upload-time = "2026-05-06T15:11:03.696Z" }, + { url = "https://files.pythonhosted.org/packages/f3/c3/0c6798456bade745c75c452342dabacce5798196483e77e643be1f53877d/orjson-3.11.9-cp314-cp314-win_amd64.whl", hash = "sha256:32ef5f4283a3be81913947d19608eacb7c6608026851123790cd9cc8982af34b", size = 127078, upload-time = "2026-05-06T15:11:05.123Z" }, + { url = "https://files.pythonhosted.org/packages/16/21/5a3f1e8913103b703a436a5664238e5b965ec392b555fe68943ea3691e6b/orjson-3.11.9-cp314-cp314-win_arm64.whl", hash = "sha256:eebdbdeef0094e4f5aefa20dcd4eb2368ab5e7a3b4edea27f1e7b2892e009cf9", size = 126687, upload-time = "2026-05-06T15:11:06.602Z" }, ] [[package]] @@ -2064,7 +2122,7 @@ wheels = [ [[package]] name = "pydantic" -version = "2.12.5" +version = "2.13.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-types" }, @@ -2072,120 +2130,125 @@ dependencies = [ { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" } +sdist = { url = "https://files.pythonhosted.org/packages/18/a5/b60d21ac674192f8ab0ba4e9fd860690f9b4a6e51ca5df118733b487d8d6/pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6", size = 844775, upload-time = "2026-05-06T13:43:05.343Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" }, + { url = "https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba", size = 472262, upload-time = "2026-05-06T13:43:02.641Z" }, ] [[package]] name = "pydantic-core" -version = "2.41.5" +version = "2.46.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/56/921726b776ace8d8f5db44c4ef961006580d91dc52b803c489fafd1aa249/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", size = 471464, upload-time = "2026-05-06T13:37:06.98Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e8/72/74a989dd9f2084b3d9530b0915fdda64ac48831c30dbf7c72a41a5232db8/pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6", size = 2105873, upload-time = "2025-11-04T13:39:31.373Z" }, - { url = "https://files.pythonhosted.org/packages/12/44/37e403fd9455708b3b942949e1d7febc02167662bf1a7da5b78ee1ea2842/pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b", size = 1899826, upload-time = "2025-11-04T13:39:32.897Z" }, - { url = "https://files.pythonhosted.org/packages/33/7f/1d5cab3ccf44c1935a359d51a8a2a9e1a654b744b5e7f80d41b88d501eec/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a", size = 1917869, upload-time = "2025-11-04T13:39:34.469Z" }, - { url = "https://files.pythonhosted.org/packages/6e/6a/30d94a9674a7fe4f4744052ed6c5e083424510be1e93da5bc47569d11810/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8", size = 2063890, upload-time = "2025-11-04T13:39:36.053Z" }, - { url = "https://files.pythonhosted.org/packages/50/be/76e5d46203fcb2750e542f32e6c371ffa9b8ad17364cf94bb0818dbfb50c/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e", size = 2229740, upload-time = "2025-11-04T13:39:37.753Z" }, - { url = "https://files.pythonhosted.org/packages/d3/ee/fed784df0144793489f87db310a6bbf8118d7b630ed07aa180d6067e653a/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1", size = 2350021, upload-time = "2025-11-04T13:39:40.94Z" }, - { url = "https://files.pythonhosted.org/packages/c8/be/8fed28dd0a180dca19e72c233cbf58efa36df055e5b9d90d64fd1740b828/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b", size = 2066378, upload-time = "2025-11-04T13:39:42.523Z" }, - { url = "https://files.pythonhosted.org/packages/b0/3b/698cf8ae1d536a010e05121b4958b1257f0b5522085e335360e53a6b1c8b/pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b", size = 2175761, upload-time = "2025-11-04T13:39:44.553Z" }, - { url = "https://files.pythonhosted.org/packages/b8/ba/15d537423939553116dea94ce02f9c31be0fa9d0b806d427e0308ec17145/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284", size = 2146303, upload-time = "2025-11-04T13:39:46.238Z" }, - { url = "https://files.pythonhosted.org/packages/58/7f/0de669bf37d206723795f9c90c82966726a2ab06c336deba4735b55af431/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594", size = 2340355, upload-time = "2025-11-04T13:39:48.002Z" }, - { url = "https://files.pythonhosted.org/packages/e5/de/e7482c435b83d7e3c3ee5ee4451f6e8973cff0eb6007d2872ce6383f6398/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e", size = 2319875, upload-time = "2025-11-04T13:39:49.705Z" }, - { url = "https://files.pythonhosted.org/packages/fe/e6/8c9e81bb6dd7560e33b9053351c29f30c8194b72f2d6932888581f503482/pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b", size = 1987549, upload-time = "2025-11-04T13:39:51.842Z" }, - { url = "https://files.pythonhosted.org/packages/11/66/f14d1d978ea94d1bc21fc98fcf570f9542fe55bfcc40269d4e1a21c19bf7/pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe", size = 2011305, upload-time = "2025-11-04T13:39:53.485Z" }, - { url = "https://files.pythonhosted.org/packages/56/d8/0e271434e8efd03186c5386671328154ee349ff0354d83c74f5caaf096ed/pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f", size = 1972902, upload-time = "2025-11-04T13:39:56.488Z" }, - { url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990, upload-time = "2025-11-04T13:39:58.079Z" }, - { url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003, upload-time = "2025-11-04T13:39:59.956Z" }, - { url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200, upload-time = "2025-11-04T13:40:02.241Z" }, - { url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578, upload-time = "2025-11-04T13:40:04.401Z" }, - { url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504, upload-time = "2025-11-04T13:40:06.072Z" }, - { url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816, upload-time = "2025-11-04T13:40:07.835Z" }, - { url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366, upload-time = "2025-11-04T13:40:09.804Z" }, - { url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698, upload-time = "2025-11-04T13:40:12.004Z" }, - { url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603, upload-time = "2025-11-04T13:40:13.868Z" }, - { url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591, upload-time = "2025-11-04T13:40:15.672Z" }, - { url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068, upload-time = "2025-11-04T13:40:17.532Z" }, - { url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908, upload-time = "2025-11-04T13:40:19.309Z" }, - { url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145, upload-time = "2025-11-04T13:40:21.548Z" }, - { url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179, upload-time = "2025-11-04T13:40:23.393Z" }, - { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" }, - { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" }, - { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" }, - { url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" }, - { url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" }, - { url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" }, - { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" }, - { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" }, - { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" }, - { url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" }, - { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" }, - { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" }, - { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" }, - { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" }, - { url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" }, - { url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" }, - { url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" }, - { url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" }, - { url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" }, - { url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" }, - { url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" }, - { url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" }, - { url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" }, - { url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" }, - { url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" }, - { url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" }, - { url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" }, - { url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" }, - { url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" }, - { url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" }, - { url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" }, - { url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" }, - { url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" }, - { url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" }, - { url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" }, - { url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" }, - { url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" }, - { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" }, - { url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" }, - { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" }, - { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" }, - { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" }, - { url = "https://files.pythonhosted.org/packages/11/72/90fda5ee3b97e51c494938a4a44c3a35a9c96c19bba12372fb9c634d6f57/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034", size = 2115441, upload-time = "2025-11-04T13:42:39.557Z" }, - { url = "https://files.pythonhosted.org/packages/1f/53/8942f884fa33f50794f119012dc6a1a02ac43a56407adaac20463df8e98f/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c", size = 1930291, upload-time = "2025-11-04T13:42:42.169Z" }, - { url = "https://files.pythonhosted.org/packages/79/c8/ecb9ed9cd942bce09fc888ee960b52654fbdbede4ba6c2d6e0d3b1d8b49c/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2", size = 1948632, upload-time = "2025-11-04T13:42:44.564Z" }, - { url = "https://files.pythonhosted.org/packages/2e/1b/687711069de7efa6af934e74f601e2a4307365e8fdc404703afc453eab26/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad", size = 2138905, upload-time = "2025-11-04T13:42:47.156Z" }, - { url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495, upload-time = "2025-11-04T13:42:49.689Z" }, - { url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388, upload-time = "2025-11-04T13:42:52.215Z" }, - { url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879, upload-time = "2025-11-04T13:42:56.483Z" }, - { url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" }, - { url = "https://files.pythonhosted.org/packages/5f/9b/1b3f0e9f9305839d7e84912f9e8bfbd191ed1b1ef48083609f0dabde978c/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26", size = 2101980, upload-time = "2025-11-04T13:43:25.97Z" }, - { url = "https://files.pythonhosted.org/packages/a4/ed/d71fefcb4263df0da6a85b5d8a7508360f2f2e9b3bf5814be9c8bccdccc1/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808", size = 1923865, upload-time = "2025-11-04T13:43:28.763Z" }, - { url = "https://files.pythonhosted.org/packages/ce/3a/626b38db460d675f873e4444b4bb030453bbe7b4ba55df821d026a0493c4/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc", size = 2134256, upload-time = "2025-11-04T13:43:31.71Z" }, - { url = "https://files.pythonhosted.org/packages/83/d9/8412d7f06f616bbc053d30cb4e5f76786af3221462ad5eee1f202021eb4e/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1", size = 2174762, upload-time = "2025-11-04T13:43:34.744Z" }, - { url = "https://files.pythonhosted.org/packages/55/4c/162d906b8e3ba3a99354e20faa1b49a85206c47de97a639510a0e673f5da/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84", size = 2143141, upload-time = "2025-11-04T13:43:37.701Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f2/f11dd73284122713f5f89fc940f370d035fa8e1e078d446b3313955157fe/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770", size = 2330317, upload-time = "2025-11-04T13:43:40.406Z" }, - { url = "https://files.pythonhosted.org/packages/88/9d/b06ca6acfe4abb296110fb1273a4d848a0bfb2ff65f3ee92127b3244e16b/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f", size = 2316992, upload-time = "2025-11-04T13:43:43.602Z" }, - { url = "https://files.pythonhosted.org/packages/36/c7/cfc8e811f061c841d7990b0201912c3556bfeb99cdcb7ed24adc8d6f8704/pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51", size = 2145302, upload-time = "2025-11-04T13:43:46.64Z" }, + { url = "https://files.pythonhosted.org/packages/5c/fa/6d7708d2cfc1a832acb6aeb0cd16e801902df8a0f583bb3b4b527fde022e/pydantic_core-2.46.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0e96592440881c74a213e5ad528e2b24d3d4f940de2766bed9010ab1d9e51594", size = 2111872, upload-time = "2026-05-06T13:40:27.596Z" }, + { url = "https://files.pythonhosted.org/packages/ae/6f/aa064a3e74b5745afbdf250594f38e7ead05e2d651bcb35994b9417a0d4d/pydantic_core-2.46.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0d65b8c354be7fb5f720c3caa8bc940bc2d20ce749c8e06135f07f8ed95dd7c", size = 1948255, upload-time = "2026-05-06T13:39:12.574Z" }, + { url = "https://files.pythonhosted.org/packages/43/3a/41114a9f7569b84b4d84e7a018c57c56347dac30c0d4a872946ec4e36c46/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bfb192b3f4b9e8a89b6277b6ce787564f62cfd272055f6e685726b111dc7826", size = 1972827, upload-time = "2026-05-06T13:38:19.841Z" }, + { url = "https://files.pythonhosted.org/packages/ef/25/1ab42e8048fe551934d9884e8d64daa7e990ad386f310a15981aeb6a5b08/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9037063db01f09b09e237c282b6792bd4da634b5402c4e7f0c61effed7701a04", size = 2041051, upload-time = "2026-05-06T13:38:10.447Z" }, + { url = "https://files.pythonhosted.org/packages/94/c2/1a934597ddf08da410385b3b7aae91956a5a76c635effef456074fad7e88/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc010ab034c8c7452522748bf937df58020d256ccae0874463d1f4d01758af8e", size = 2221314, upload-time = "2026-05-06T13:40:13.089Z" }, + { url = "https://files.pythonhosted.org/packages/02/6d/9e8ad178c9c4df27ad3c8f25d1fe2a7ab0d2ba0559fad4aee5d3d1f16771/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5dac79fa1614d1e06ca695109c6105923bd9c7d1d6c918d4e637b7e6b32fd3", size = 2285146, upload-time = "2026-05-06T13:38:59.224Z" }, + { url = "https://files.pythonhosted.org/packages/80/50/540cd3aeefc041beb111125c4bff779831a2111fc6b15a9138cda277d32c/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9fa868638bf362d3d138ea55829cefb3d5f4b0d7f142234382a15e2485dbec4", size = 2089685, upload-time = "2026-05-06T13:38:17.762Z" }, + { url = "https://files.pythonhosted.org/packages/6b/a4/b440ad35f05f6a38f89fa0f149accb3f0e02be94ca5e15f3c449a61b4bc9/pydantic_core-2.46.4-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:17299feefe090f2caa5b8e37222bb5f663e4935a8bfa6931d4102e5df1a9f398", size = 2115420, upload-time = "2026-05-06T13:37:58.195Z" }, + { url = "https://files.pythonhosted.org/packages/99/61/de4f55db8dfd57bfdfa9a12ec90fe1b57c4f41062f7ca86f08586b3e0ac0/pydantic_core-2.46.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4c63ebc82684aa89d9a3bcbd13d515b3be44250dc68dd3bd81526c1cb31286c3", size = 2165122, upload-time = "2026-05-06T13:37:01.167Z" }, + { url = "https://files.pythonhosted.org/packages/f7/52/7c529d7bdb2d1068bd52f51fe32572c8301f9a4febf1948f10639f1436f5/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:aaa2a54443eff1950ba5ddc6b6ccda0d9c84a364276a62f969bdf2a390650848", size = 2182573, upload-time = "2026-05-06T13:38:45.04Z" }, + { url = "https://files.pythonhosted.org/packages/37/b3/7c40325848ba78247f2812dcf9c7274e38cd801820ca6dd9fe63bcfb0eb4/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:18e5ceec2ab67e6d5f1a9085e5a24c9c4e2ac4545730bfe668680bca05e555f3", size = 2317139, upload-time = "2026-05-06T13:37:15.539Z" }, + { url = "https://files.pythonhosted.org/packages/d9/37/f913f81a657c865b75da6c0dbed79876073c2a43b5bd9edbe8da785e4d49/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a0f62d0a58f4e7da165457e995725421e0064f2255d8eccebc49f41bbc23b109", size = 2360433, upload-time = "2026-05-06T13:37:30.099Z" }, + { url = "https://files.pythonhosted.org/packages/c4/67/6acaa1be2567f9256b056d8477158cac7240813956ce86e49deae8e173b4/pydantic_core-2.46.4-cp311-cp311-win32.whl", hash = "sha256:041bde0a48fd37cf71cab1c9d56d3e8625a3793fef1f7dd232b3ff37e978ecda", size = 1985513, upload-time = "2026-05-06T13:38:15.669Z" }, + { url = "https://files.pythonhosted.org/packages/aa/e6/c505f83dfeda9a2e5c995cfd872949e4d05e12f7feb3dca72f633daefa94/pydantic_core-2.46.4-cp311-cp311-win_amd64.whl", hash = "sha256:6f2eeda33a839975441c86a4119e1383c50b47faf0cbb5176985565c6bb02c33", size = 2071114, upload-time = "2026-05-06T13:40:35.416Z" }, + { url = "https://files.pythonhosted.org/packages/0f/da/7a263a96d965d9d0df5e8de8a475f33495451117035b09acb110288c381f/pydantic_core-2.46.4-cp311-cp311-win_arm64.whl", hash = "sha256:14f4c5d6db102bd796a627bbb3a17b4cf4574b9ae861d8b7c9a9661c6dd3362d", size = 2044298, upload-time = "2026-05-06T13:38:29.754Z" }, + { url = "https://files.pythonhosted.org/packages/ce/8c/af022f0af448d7747c5154288d46b5f2bc5f17366eaa0e23e9aa04d59f3b/pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2", size = 2106158, upload-time = "2026-05-06T13:38:57.215Z" }, + { url = "https://files.pythonhosted.org/packages/19/95/6195171e385007300f0f5574592e467c568becce2d937a0b6804f218bc49/pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f", size = 1951724, upload-time = "2026-05-06T13:37:02.697Z" }, + { url = "https://files.pythonhosted.org/packages/8e/bc/f47d1ff9cbb1620e1b5b697eef06010035735f07820180e74178226b27b3/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7", size = 1975742, upload-time = "2026-05-06T13:37:09.448Z" }, + { url = "https://files.pythonhosted.org/packages/5b/11/9b9a5b0306345664a2da6410877af6e8082481b5884b3ddd78d47c6013ce/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7", size = 2052418, upload-time = "2026-05-06T13:37:38.234Z" }, + { url = "https://files.pythonhosted.org/packages/f1/b7/a65fec226f5d78fc39f4a13c4cc0c768c22b113438f60c14adc9d2865038/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712", size = 2232274, upload-time = "2026-05-06T13:38:27.753Z" }, + { url = "https://files.pythonhosted.org/packages/68/f0/92039db98b907ef49269a8271f67db9cb78ae2fc68062ef7e4e77adb5f61/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4", size = 2309940, upload-time = "2026-05-06T13:38:05.353Z" }, + { url = "https://files.pythonhosted.org/packages/5f/97/2aab507d3d00ca626e8e57c1eac6a79e4e5fbcc63eb99733ff55d1717f65/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce", size = 2094516, upload-time = "2026-05-06T13:39:10.577Z" }, + { url = "https://files.pythonhosted.org/packages/22/37/a8aca44d40d737dde2bc05b3c6c07dff0de07ce6f82e9f3167aeaf4d5dea/pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987", size = 2136854, upload-time = "2026-05-06T13:40:22.59Z" }, + { url = "https://files.pythonhosted.org/packages/24/99/fcef1b79238c06a8cbec70819ac722ba76e02bc8ada9b0fd66eba40da01b/pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b", size = 2180306, upload-time = "2026-05-06T13:40:10.666Z" }, + { url = "https://files.pythonhosted.org/packages/ae/6c/fc44000918855b42779d007ae63b0532794739027b2f417321cddbc44f6a/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458", size = 2190044, upload-time = "2026-05-06T13:40:43.231Z" }, + { url = "https://files.pythonhosted.org/packages/6b/65/d9cadc9f1920d7a127ad2edba16c1db7916e59719285cd6c94600b0080ba/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b", size = 2329133, upload-time = "2026-05-06T13:39:57.365Z" }, + { url = "https://files.pythonhosted.org/packages/d0/cf/c873d91679f3a30bcf5e7ac280ce5573483e72295307685120d0d5ad3416/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c", size = 2374464, upload-time = "2026-05-06T13:38:06.976Z" }, + { url = "https://files.pythonhosted.org/packages/47/bd/6f2fc8188f31bf10590f1e98e7b306336161fac930a8c514cd7bd828c7dc/pydantic_core-2.46.4-cp312-cp312-win32.whl", hash = "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894", size = 1974823, upload-time = "2026-05-06T13:40:47.985Z" }, + { url = "https://files.pythonhosted.org/packages/40/8c/985c1d41ea1107c2534abd9870e4ed5c8e7669b5c308297835c001e7a1c4/pydantic_core-2.46.4-cp312-cp312-win_amd64.whl", hash = "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89", size = 2072919, upload-time = "2026-05-06T13:39:21.153Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ba/f463d006e0c47373ca7ec5e1a261c59dc01ef4d62b2657af925fb0deee3a/pydantic_core-2.46.4-cp312-cp312-win_arm64.whl", hash = "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a", size = 2027604, upload-time = "2026-05-06T13:39:03.753Z" }, + { url = "https://files.pythonhosted.org/packages/51/a2/5d30b469c5267a17b39dec53208222f76a8d351dfac4af661888c5aee77d/pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008", size = 2106306, upload-time = "2026-05-06T13:37:48.029Z" }, + { url = "https://files.pythonhosted.org/packages/c1/81/4fa520eaffa8bd7d1525e644cd6d39e7d60b1592bc5b516693c7340b50f1/pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4", size = 1951906, upload-time = "2026-05-06T13:37:17.012Z" }, + { url = "https://files.pythonhosted.org/packages/03/d5/fd02da45b659668b05923b17ba3a0100a0a3d5541e3bd8fcc4ecb711309e/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76", size = 1976802, upload-time = "2026-05-06T13:37:35.113Z" }, + { url = "https://files.pythonhosted.org/packages/21/f2/95727e1368be3d3ed485eaab7adbd7dda408f33f7a36e8b48e0144002b91/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3", size = 2052446, upload-time = "2026-05-06T13:37:12.313Z" }, + { url = "https://files.pythonhosted.org/packages/9c/86/5d99feea3f77c7234b8718075b23db11532773c1a0dbd9b9490215dc2eeb/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76", size = 2232757, upload-time = "2026-05-06T13:39:01.149Z" }, + { url = "https://files.pythonhosted.org/packages/d2/3a/508ac615935ef7588cf6d9e9b91309fdc2da751af865e02a9098de88258c/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4", size = 2309275, upload-time = "2026-05-06T13:37:41.406Z" }, + { url = "https://files.pythonhosted.org/packages/07/f8/41db9de19d7987d6b04715a02b3b40aea467000275d9d758ffaa31af7d50/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a", size = 2094467, upload-time = "2026-05-06T13:39:18.847Z" }, + { url = "https://files.pythonhosted.org/packages/2c/e2/f35033184cb11d0052daf4416e8e10a502ea2ac006fc4f459aee872727d1/pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262", size = 2134417, upload-time = "2026-05-06T13:40:17.944Z" }, + { url = "https://files.pythonhosted.org/packages/7e/7b/6ceeb1cc90e193862f444ebe373d8fdf613f0a82572dde03fb10734c6c71/pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e", size = 2179782, upload-time = "2026-05-06T13:40:32.618Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f2/c8d7773ede6af08036423a00ae0ceffce266c3c52a096c435d68c896083f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd", size = 2188782, upload-time = "2026-05-06T13:36:51.018Z" }, + { url = "https://files.pythonhosted.org/packages/59/31/0c864784e31f09f05cdd87606f08923b9c9e7f6e51dd27f20f62f975ce9f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be", size = 2328334, upload-time = "2026-05-06T13:40:37.764Z" }, + { url = "https://files.pythonhosted.org/packages/c2/eb/4f6c8a41efa30baa755590f4141abf3a8c370fab610915733e74134a7270/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d", size = 2372986, upload-time = "2026-05-06T13:39:34.152Z" }, + { url = "https://files.pythonhosted.org/packages/5b/24/b375a480d53113860c299764bfe9f349a3dc9108b3adc0d7f0d786492ebf/pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb", size = 1973693, upload-time = "2026-05-06T13:37:55.072Z" }, + { url = "https://files.pythonhosted.org/packages/7e/e8/cff247591966f2d22ec8c003cd7587e27b7ba7b81ab2fb888e3ab75dc285/pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292", size = 2071819, upload-time = "2026-05-06T13:38:49.139Z" }, + { url = "https://files.pythonhosted.org/packages/c6/1a/f4aee670d5670e9e148e0c82c7db98d780be566c6e6a97ee8035528ca0b3/pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d", size = 2027411, upload-time = "2026-05-06T13:40:45.796Z" }, + { url = "https://files.pythonhosted.org/packages/8d/74/228a26ddad29c6672b805d9fd78e8d251cd04004fa7eed0e622096cd0250/pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb", size = 2102079, upload-time = "2026-05-06T13:38:41.019Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/8970b150a4b4365623ae00fc88603491f763c627311ae8031e3111356d6e/pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462", size = 1952179, upload-time = "2026-05-06T13:36:59.812Z" }, + { url = "https://files.pythonhosted.org/packages/95/30/5211a831ae054928054b2f79731661087a2bc5c01e825c672b3a4a8f1b3e/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9", size = 1978926, upload-time = "2026-05-06T13:37:39.933Z" }, + { url = "https://files.pythonhosted.org/packages/57/e9/689668733b1eb67adeef047db3c2e8788fcf65a7fd9c9e2b46b7744fe245/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4", size = 2046785, upload-time = "2026-05-06T13:38:01.995Z" }, + { url = "https://files.pythonhosted.org/packages/60/d9/6715260422ff50a2109878fd24d948a6c3446bb2664f34ee78cd972b3acd/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914", size = 2228733, upload-time = "2026-05-06T13:40:50.371Z" }, + { url = "https://files.pythonhosted.org/packages/18/ae/fdb2f64316afca925640f8e70bb1a564b0ec2721c1389e25b8eb4bf9a299/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28", size = 2307534, upload-time = "2026-05-06T13:37:21.531Z" }, + { url = "https://files.pythonhosted.org/packages/89/1d/8eff589b45bb8190a9d12c49cfad0f176a5cbd1534908a6b5125e2886239/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b", size = 2099732, upload-time = "2026-05-06T13:39:31.942Z" }, + { url = "https://files.pythonhosted.org/packages/06/d5/ee5a3366637fee41dee51a1fc91562dcf12ddbc68fda34e6b253da2324bb/pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c", size = 2129627, upload-time = "2026-05-06T13:37:25.033Z" }, + { url = "https://files.pythonhosted.org/packages/94/33/2414be571d2c6a6c4d08be21f9292b6d3fdb08949a97b6dfe985017821db/pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb", size = 2179141, upload-time = "2026-05-06T13:37:14.046Z" }, + { url = "https://files.pythonhosted.org/packages/7b/79/7daa95be995be0eecc4cf75064cb33f9bbbfe3fe0158caf2f0d4a996a5c7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898", size = 2184325, upload-time = "2026-05-06T13:36:53.615Z" }, + { url = "https://files.pythonhosted.org/packages/9f/cb/d0a382f5c0de8a222dc61c65348e0ce831b1f68e0a018450d31c2cace3a5/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e", size = 2323990, upload-time = "2026-05-06T13:40:29.971Z" }, + { url = "https://files.pythonhosted.org/packages/05/db/d9ba624cc4a5aced1598e88c04fdbd8310c8a69b9d38b9a3d39ce3a61ed7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519", size = 2369978, upload-time = "2026-05-06T13:37:23.027Z" }, + { url = "https://files.pythonhosted.org/packages/f2/20/d15df15ba918c423461905802bfd2981c3af0bfa0e40d05e13edbfa48bc3/pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4", size = 1966354, upload-time = "2026-05-06T13:38:03.499Z" }, + { url = "https://files.pythonhosted.org/packages/fc/b6/6b8de4c0a7d7ab3004c439c80c5c1e0a3e8d78bbae19379b01960383d9e5/pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac", size = 2072238, upload-time = "2026-05-06T13:39:40.807Z" }, + { url = "https://files.pythonhosted.org/packages/32/36/51eb763beec1f4cf59b1db243a7dcc39cbb41230f050a09b9d69faaf0a48/pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a", size = 2018251, upload-time = "2026-05-06T13:37:26.72Z" }, + { url = "https://files.pythonhosted.org/packages/e8/91/855af51d625b23aa987116a19e231d2aaef9c4a415273ddc189b79a45fee/pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0", size = 2099593, upload-time = "2026-05-06T13:39:47.682Z" }, + { url = "https://files.pythonhosted.org/packages/fb/1b/8784a54c65edb5f49f0a14d6977cf1b209bba85a4c77445b255c2de58ab3/pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d", size = 1935226, upload-time = "2026-05-06T13:40:40.428Z" }, + { url = "https://files.pythonhosted.org/packages/e8/e7/1955d28d1afc56dd4b3ad7cc0cf39df1b9852964cf16e5d13912756d6d6b/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b", size = 1974605, upload-time = "2026-05-06T13:37:32.029Z" }, + { url = "https://files.pythonhosted.org/packages/93/e2/3fedbf0ba7a22850e6e9fd78117f1c0f10f950182344d8a6c535d468fdd8/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000", size = 2030777, upload-time = "2026-05-06T13:38:55.239Z" }, + { url = "https://files.pythonhosted.org/packages/f8/61/46be275fcaaba0b4f5b9669dd852267ce1ff616592dccf7a7845588df091/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e", size = 2236641, upload-time = "2026-05-06T13:37:08.096Z" }, + { url = "https://files.pythonhosted.org/packages/60/db/12e93e46a8bac9988be3c016860f83293daea8c716c029c9ace279036f2f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd", size = 2286404, upload-time = "2026-05-06T13:40:20.221Z" }, + { url = "https://files.pythonhosted.org/packages/e2/4a/4d8b19008f38d31c53b8219cfedc2e3d5de5fe99d90076b7e767de29274f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3", size = 2109219, upload-time = "2026-05-06T13:38:12.153Z" }, + { url = "https://files.pythonhosted.org/packages/88/70/3cbc40978fefb7bb09c6708d40d4ad1a5d70fd7213c3d17f971de868ec1f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7", size = 2110594, upload-time = "2026-05-06T13:40:02.971Z" }, + { url = "https://files.pythonhosted.org/packages/9d/20/b8d36736216e29491125531685b2f9e61aa5b4b2599893f8268551da3338/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff", size = 2159542, upload-time = "2026-05-06T13:39:27.506Z" }, + { url = "https://files.pythonhosted.org/packages/1d/a2/367df868eb584dacf6bf82a389272406d7178e301c4ac82545ab98bc2dd9/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424", size = 2168146, upload-time = "2026-05-06T13:38:31.93Z" }, + { url = "https://files.pythonhosted.org/packages/c1/b8/4460f77f7e201893f649a29ab355dddd3beee8a97bcb1a320db414f9a06e/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6", size = 2306309, upload-time = "2026-05-06T13:37:44.717Z" }, + { url = "https://files.pythonhosted.org/packages/64/c4/be2639293acd87dc8ddbcec41a73cee9b2ebf996fe6d892a1a74e88ad3f7/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565", size = 2369736, upload-time = "2026-05-06T13:37:05.645Z" }, + { url = "https://files.pythonhosted.org/packages/30/a6/9f9f380dbb301f67023bf8f707aaa75daadf84f7152d95c410fd7e81d994/pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02", size = 1955575, upload-time = "2026-05-06T13:38:51.116Z" }, + { url = "https://files.pythonhosted.org/packages/40/1f/f1eb9eb350e795d1af8586289746f5c5677d16043040d63710e22abc43c9/pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5", size = 2051624, upload-time = "2026-05-06T13:38:21.672Z" }, + { url = "https://files.pythonhosted.org/packages/f6/d2/42dd53d0a85c27606f316d3aa5d2869c4e8470a5ed6dec30e4a1abe19192/pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596", size = 2017325, upload-time = "2026-05-06T13:40:52.723Z" }, + { url = "https://files.pythonhosted.org/packages/ee/a4/73995fd4ebbb46ba0ee51e6fa049b8f02c40daebb762208feda8a6b7894d/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:14d4edf427bdcf950a8a02d7cb44a08614388dd6e1bdcbf4f67504fa7887da9c", size = 2111589, upload-time = "2026-05-06T13:37:10.817Z" }, + { url = "https://files.pythonhosted.org/packages/fb/7f/f37d3a5e8bfcc2e403f5c57a730f2d815693fb42119e8ea48b3789335af1/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:0ce40cd7b21210e99342afafbd4d0f76d784eb5b1d60f3bdc566be4983c6c73b", size = 1944552, upload-time = "2026-05-06T13:36:56.717Z" }, + { url = "https://files.pythonhosted.org/packages/15/3c/d7eb777b3ff43e8433a4efb39a17aa8fd98a4ee8561a24a67ef5db07b2d6/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90884113d8b48f760e9587002789ddd741e76ab9f89518cd1e43b1f1a52ec44b", size = 1982984, upload-time = "2026-05-06T13:39:06.207Z" }, + { url = "https://files.pythonhosted.org/packages/63/87/70b9f40170a81afd55ca26c9b2acb25c20d64bcfbf888fafecb3ba077d4c/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66ce7632c22d837c95301830e111ad0128a32b8207533b60896a96c4915192ea", size = 2138417, upload-time = "2026-05-06T13:39:45.476Z" }, + { url = "https://files.pythonhosted.org/packages/9d/1d/8987ad40f65ae1432753072f214fb5c74fe47ffbd0698bb9cbbb585664f8/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7", size = 2095527, upload-time = "2026-05-06T13:39:52.283Z" }, + { url = "https://files.pythonhosted.org/packages/64/d3/84c282a7eee1d3ac4c0377546ef5a1ea436ce26840d9ac3b7ed54a377507/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df", size = 1936024, upload-time = "2026-05-06T13:40:15.671Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ca/eac61596cdeb4d7e174d3dc0bd8a6238f14f75f97a24e7b7db4c7e7340a0/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526", size = 1990696, upload-time = "2026-05-06T13:38:34.717Z" }, + { url = "https://files.pythonhosted.org/packages/fa/c3/7c8b240552251faf6b3a957db200fcfbbcec36763c050428b601e0c9b83b/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0", size = 2147590, upload-time = "2026-05-06T13:39:29.883Z" }, + { url = "https://files.pythonhosted.org/packages/11/cb/428de0385b6c8d44b716feba566abfacfbd23ee3c4439faa789a1456242f/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0c563b08bca408dc7f65f700633d8442fffb2421fc47b8101377e9fd65051ff0", size = 2112782, upload-time = "2026-05-06T13:37:04.016Z" }, + { url = "https://files.pythonhosted.org/packages/0b/b5/6a17bdadd0fc1f170adfd05a20d37c832f52b117b4d9131da1f41bb097ce/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:db06ffe51636ffe9ca531fe9023dd64bdd794be8754cb5df57c5498ae5b518a7", size = 1952146, upload-time = "2026-05-06T13:39:43.092Z" }, + { url = "https://files.pythonhosted.org/packages/2a/dc/03734d80e362cd43ef65428e9de77c730ce7f2f11c60d2b1e1b39f0fbf99/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:133878133d271ade3d41d1bfb2a45ec38dbdbda40bc065921c6b04e4630127e2", size = 2134492, upload-time = "2026-05-06T13:36:58.124Z" }, + { url = "https://files.pythonhosted.org/packages/de/df/5e5ffc085ed07cc22d298134d3d911c63e91f6a0eb91fe646750a3209910/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9bc519fbf2b7578398853d815009ae5e4d4603d12f4e3f91da8c06852d3da3e9", size = 2156604, upload-time = "2026-05-06T13:37:49.88Z" }, + { url = "https://files.pythonhosted.org/packages/81/44/6e112a4253e56f5705467cbab7ab5e91ee7398ba3d56d358635958893d3e/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c7a7bd4e39e8e4c12c39cd480356842b6a8a06e41b23a55a5e3e191718838ddf", size = 2183828, upload-time = "2026-05-06T13:37:43.053Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ad/5565071e937d8e752842ac241463944c9eb14c87e2d269f2658a5bd05e98/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:d396ec2b979760aaf3218e76c24e65bd0aca24983298653b3a9d7a45f9e47b30", size = 2310000, upload-time = "2026-05-06T13:37:56.694Z" }, + { url = "https://files.pythonhosted.org/packages/4f/c3/66883a5cec183e7fba4d024b4cbbe61851a63750ef606b0afecc46d1f2bf/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:86e1a4418c6cd97d60c95c71164158eaf7324fae7b0923264016baa993eba6fc", size = 2361286, upload-time = "2026-05-06T13:40:05.667Z" }, + { url = "https://files.pythonhosted.org/packages/4b/2d/69abac8f838090bbecd5df894befb2c2619e7996a98ddb949db9f3b93225/pydantic_core-2.46.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:d51026d73fcfd93610abc7b27789c26b313920fcfb20e27462d74a7f8b06e983", size = 2193071, upload-time = "2026-05-06T13:38:08.682Z" }, ] [[package]] name = "pydantic-settings" -version = "2.13.1" +version = "2.14.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, { name = "python-dotenv" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/52/6d/fffca34caecc4a3f97bda81b2098da5e8ab7efc9a66e819074a11955d87e/pydantic_settings-2.13.1.tar.gz", hash = "sha256:b4c11847b15237fb0171e1462bf540e294affb9b86db4d9aa5c01730bdbe4025", size = 223826, upload-time = "2026-02-19T13:45:08.055Z" } +sdist = { url = "https://files.pythonhosted.org/packages/07/60/1d1e59c9c90d54591469ada7d268251f71c24bdb765f1a8a832cee8c6653/pydantic_settings-2.14.1.tar.gz", hash = "sha256:e874d3bec7e787b0c9958277956ed9b4dd5de6a80e162188fdaff7c5e26fd5fa", size = 235551, upload-time = "2026-05-08T13:40:06.542Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/00/4b/ccc026168948fec4f7555b9164c724cf4125eac006e176541483d2c959be/pydantic_settings-2.13.1-py3-none-any.whl", hash = "sha256:d56fd801823dbeae7f0975e1f8c8e25c258eb75d278ea7abb5d9cebb01b56237", size = 58929, upload-time = "2026-02-19T13:45:06.034Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8d/f1af3832f5e6eb13ba94ee809e72b8ecb5eef226d27ee0bef7d963d943c7/pydantic_settings-2.14.1-py3-none-any.whl", hash = "sha256:6e3c7edfd8277687cdc598f56e5cff0e9bfff0910a3749deaa8d4401c3a2b9de", size = 60964, upload-time = "2026-05-08T13:40:04.958Z" }, ] [[package]] @@ -2224,15 +2287,15 @@ wheels = [ [[package]] name = "pytest-asyncio" -version = "1.3.0" +version = "1.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pytest" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/90/2c/8af215c0f776415f3590cac4f9086ccefd6fd463befeae41cd4d3f193e5a/pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5", size = 50087, upload-time = "2025-11-10T16:07:47.256Z" } +sdist = { url = "https://files.pythonhosted.org/packages/43/7c/d36d04db312ecf4298932ef77e6e4a9e8ad017906e24e34f0b0c361a2473/pytest_asyncio-1.4.0.tar.gz", hash = "sha256:c6c0d2259945122819f171a32ecea2c349ead889ee28176caaf492143424be42", size = 58514, upload-time = "2026-05-26T09:56:04.083Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/35/f8b19922b6a25bc0880171a2f1a003eaeb93657475193ab516fd87cac9da/pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5", size = 15075, upload-time = "2025-11-10T16:07:45.537Z" }, + { url = "https://files.pythonhosted.org/packages/03/e2/08a497ef684b88559c9cc5f4ad53a37e7b99e727094a86d6ea32536d5d3c/pytest_asyncio-1.4.0-py3-none-any.whl", hash = "sha256:933ca923a23075a87fb7070c0ec272a6848489824d887c85c812670932835aa1", size = 16930, upload-time = "2026-05-26T09:56:02.576Z" }, ] [[package]] @@ -2296,11 +2359,11 @@ wheels = [ [[package]] name = "python-multipart" -version = "0.0.26" +version = "0.0.30" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/88/71/b145a380824a960ebd60e1014256dbb7d2253f2316ff2d73dfd8928ec2c3/python_multipart-0.0.26.tar.gz", hash = "sha256:08fadc45918cd615e26846437f50c5d6d23304da32c341f289a617127b081f17", size = 43501, upload-time = "2026-04-10T14:09:59.473Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/82/c8cd43a6e0719bf5a3b034f6726dd701f75829c08944c83d4b95d02ed0e8/python_multipart-0.0.30.tar.gz", hash = "sha256:0edfe0475c1f46ddd3ff7785a626f6118af32bdcf359bb21260367313bb32118", size = 46316, upload-time = "2026-05-31T19:24:55.198Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/22/f1925cdda983ab66fc8ec6ec8014b959262747e58bdca26a4e3d1da29d56/python_multipart-0.0.26-py3-none-any.whl", hash = "sha256:c0b169f8c4484c13b0dcf2ef0ec3a4adb255c4b7d18d8e420477d2b1dd03f185", size = 28847, upload-time = "2026-04-10T14:09:58.131Z" }, + { url = "https://files.pythonhosted.org/packages/1c/fd/0318007beb234790993d3ec5afd051d1dbceb733e81e3afe2b981ece3f37/python_multipart-0.0.30-py3-none-any.whl", hash = "sha256:830964def8c90607ac5daa00514e3987815865713ade8d20febc9177ac0c3c5b", size = 29730, upload-time = "2026-05-31T19:24:53.814Z" }, ] [[package]] @@ -2561,27 +2624,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.10" +version = "0.15.15" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/d9/aa3f7d59a10ef6b14fe3431706f854dbf03c5976be614a9796d36326810c/ruff-0.15.10.tar.gz", hash = "sha256:d1f86e67ebfdef88e00faefa1552b5e510e1d35f3be7d423dc7e84e63788c94e", size = 4631728, upload-time = "2026-04-09T14:06:09.884Z" } +sdist = { url = "https://files.pythonhosted.org/packages/84/6f/a76f7d96e5c962f5b69cee865e49c15c1116897c01990faa8a57edb62e7f/ruff-0.15.15.tar.gz", hash = "sha256:b8dff018130b46d8e5bf0f926ef6b60cf871d6d5ae45fc9334e09632daa741d6", size = 4706985, upload-time = "2026-05-28T14:16:57.784Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/eb/00/a1c2fdc9939b2c03691edbda290afcd297f1f389196172826b03d6b6a595/ruff-0.15.10-py3-none-linux_armv6l.whl", hash = "sha256:0744e31482f8f7d0d10a11fcbf897af272fefdfcb10f5af907b18c2813ff4d5f", size = 10563362, upload-time = "2026-04-09T14:06:21.189Z" }, - { url = "https://files.pythonhosted.org/packages/5c/15/006990029aea0bebe9d33c73c3e28c80c391ebdba408d1b08496f00d422d/ruff-0.15.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b1e7c16ea0ff5a53b7c2df52d947e685973049be1cdfe2b59a9c43601897b22e", size = 10951122, upload-time = "2026-04-09T14:06:02.236Z" }, - { url = "https://files.pythonhosted.org/packages/f2/c0/4ac978fe874d0618c7da647862afe697b281c2806f13ce904ad652fa87e4/ruff-0.15.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:93cc06a19e5155b4441dd72808fdf84290d84ad8a39ca3b0f994363ade4cebb1", size = 10314005, upload-time = "2026-04-09T14:06:00.026Z" }, - { url = "https://files.pythonhosted.org/packages/da/73/c209138a5c98c0d321266372fc4e33ad43d506d7e5dd817dd89b60a8548f/ruff-0.15.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83e1dd04312997c99ea6965df66a14fb4f03ba978564574ffc68b0d61fd3989e", size = 10643450, upload-time = "2026-04-09T14:05:42.137Z" }, - { url = "https://files.pythonhosted.org/packages/ec/76/0deec355d8ec10709653635b1f90856735302cb8e149acfdf6f82a5feb70/ruff-0.15.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8154d43684e4333360fedd11aaa40b1b08a4e37d8ffa9d95fee6fa5b37b6fab1", size = 10379597, upload-time = "2026-04-09T14:05:49.984Z" }, - { url = "https://files.pythonhosted.org/packages/dc/be/86bba8fc8798c081e28a4b3bb6d143ccad3fd5f6f024f02002b8f08a9fa3/ruff-0.15.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ab88715f3a6deb6bde6c227f3a123410bec7b855c3ae331b4c006189e895cef", size = 11146645, upload-time = "2026-04-09T14:06:12.246Z" }, - { url = "https://files.pythonhosted.org/packages/a8/89/140025e65911b281c57be1d385ba1d932c2366ca88ae6663685aed8d4881/ruff-0.15.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a768ff5969b4f44c349d48edf4ab4f91eddb27fd9d77799598e130fb628aa158", size = 12030289, upload-time = "2026-04-09T14:06:04.776Z" }, - { url = "https://files.pythonhosted.org/packages/88/de/ddacca9545a5e01332567db01d44bd8cf725f2db3b3d61a80550b48308ea/ruff-0.15.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ee3ef42dab7078bda5ff6a1bcba8539e9857deb447132ad5566a038674540d0", size = 11496266, upload-time = "2026-04-09T14:05:55.485Z" }, - { url = "https://files.pythonhosted.org/packages/bc/bb/7ddb00a83760ff4a83c4e2fc231fd63937cc7317c10c82f583302e0f6586/ruff-0.15.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51cb8cc943e891ba99989dd92d61e29b1d231e14811db9be6440ecf25d5c1609", size = 11256418, upload-time = "2026-04-09T14:05:57.69Z" }, - { url = "https://files.pythonhosted.org/packages/dc/8d/55de0d35aacf6cd50b6ee91ee0f291672080021896543776f4170fc5c454/ruff-0.15.10-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:e59c9bdc056a320fb9ea1700a8d591718b8faf78af065484e801258d3a76bc3f", size = 11288416, upload-time = "2026-04-09T14:05:44.695Z" }, - { url = "https://files.pythonhosted.org/packages/68/cf/9438b1a27426ec46a80e0a718093c7f958ef72f43eb3111862949ead3cc1/ruff-0.15.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:136c00ca2f47b0018b073f28cb5c1506642a830ea941a60354b0e8bc8076b151", size = 10621053, upload-time = "2026-04-09T14:05:52.782Z" }, - { url = "https://files.pythonhosted.org/packages/4c/50/e29be6e2c135e9cd4cb15fbade49d6a2717e009dff3766dd080fcb82e251/ruff-0.15.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8b80a2f3c9c8a950d6237f2ca12b206bccff626139be9fa005f14feb881a1ae8", size = 10378302, upload-time = "2026-04-09T14:06:14.361Z" }, - { url = "https://files.pythonhosted.org/packages/18/2f/e0b36a6f99c51bb89f3a30239bc7bf97e87a37ae80aa2d6542d6e5150364/ruff-0.15.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:e3e53c588164dc025b671c9df2462429d60357ea91af7e92e9d56c565a9f1b07", size = 10850074, upload-time = "2026-04-09T14:06:16.581Z" }, - { url = "https://files.pythonhosted.org/packages/11/08/874da392558ce087a0f9b709dc6ec0d60cbc694c1c772dab8d5f31efe8cb/ruff-0.15.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b0c52744cf9f143a393e284125d2576140b68264a93c6716464e129a3e9adb48", size = 11358051, upload-time = "2026-04-09T14:06:18.948Z" }, - { url = "https://files.pythonhosted.org/packages/e4/46/602938f030adfa043e67112b73821024dc79f3ab4df5474c25fa4c1d2d14/ruff-0.15.10-py3-none-win32.whl", hash = "sha256:d4272e87e801e9a27a2e8df7b21011c909d9ddd82f4f3281d269b6ba19789ca5", size = 10588964, upload-time = "2026-04-09T14:06:07.14Z" }, - { url = "https://files.pythonhosted.org/packages/25/b6/261225b875d7a13b33a6d02508c39c28450b2041bb01d0f7f1a83d569512/ruff-0.15.10-py3-none-win_amd64.whl", hash = "sha256:28cb32d53203242d403d819fd6983152489b12e4a3ae44993543d6fe62ab42ed", size = 11745044, upload-time = "2026-04-09T14:05:39.473Z" }, - { url = "https://files.pythonhosted.org/packages/58/ed/dea90a65b7d9e69888890fb14c90d7f51bf0c1e82ad800aeb0160e4bacfd/ruff-0.15.10-py3-none-win_arm64.whl", hash = "sha256:601d1610a9e1f1c2165a4f561eeaa2e2ea1e97f3287c5aa258d3dab8b57c6188", size = 11035607, upload-time = "2026-04-09T14:05:47.593Z" }, + { url = "https://files.pythonhosted.org/packages/fa/9d/3a45c05b8ab04b4705989de70a79008e27c8003296a0feaee9edc18dd7e9/ruff-0.15.15-py3-none-linux_armv6l.whl", hash = "sha256:cf93e5388f412e1b108b1f8b34a6e036b70fe8aff89393befad96fe48670311b", size = 10710652, upload-time = "2026-05-28T14:16:06.701Z" }, + { url = "https://files.pythonhosted.org/packages/05/66/da974431624bf3b49f6ee1f9543c02d929ff1cba78b0d5a79c38cf21f744/ruff-0.15.15-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ac5a646d1f6a7dadd5d50842dae2c1f9862ac887ef5d1b1375e02def791fde6e", size = 11096615, upload-time = "2026-05-28T14:16:23.313Z" }, + { url = "https://files.pythonhosted.org/packages/8c/09/7443452e5d290230a712103f2fdceeef7184f3ec99a2bd01c8be78aaceb5/ruff-0.15.15-py3-none-macosx_11_0_arm64.whl", hash = "sha256:77d955a431430c66f72dd94e379ad38a16daea3d25094872ac4edf9e797be530", size = 10436683, upload-time = "2026-05-28T14:16:40.974Z" }, + { url = "https://files.pythonhosted.org/packages/53/01/d330c26a57fa4f3943a14424904027428315b700fe4d14a84bb123a649e5/ruff-0.15.15-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7614ee79c69788cf6cedd568069ade9cecc22a1ad20494efe8d0c9ebb4b622d4", size = 10769064, upload-time = "2026-05-28T14:16:28.905Z" }, + { url = "https://files.pythonhosted.org/packages/1d/85/cc8770f8bdff541b1da8392d1634141fe4a0e3f4ee596605959b7906c27f/ruff-0.15.15-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3cdb1679e06a1f6b47bc384714ae96f6e2fb65ca441eb78c43d2ca554176ce1f", size = 10511987, upload-time = "2026-05-28T14:16:43.732Z" }, + { url = "https://files.pythonhosted.org/packages/7c/29/8c190c1472b63013583ba391f3342036e02010544c1270455ed8e519bdf3/ruff-0.15.15-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2728b93d7b23a603ea2c0ac6eb73d760bd38ec9de35f35fb41e18f7a3fee7622", size = 11275100, upload-time = "2026-05-28T14:16:55.244Z" }, + { url = "https://files.pythonhosted.org/packages/9f/6b/7e145ce2cc8e63d6834eca03d83a0e18d121def5c69f91b4cf4011ed4879/ruff-0.15.15-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be582fcc0db438902c7792b08d6ddf6c9b9e21addaa10092c2c741cfb09e5a45", size = 12176903, upload-time = "2026-05-28T14:16:14.368Z" }, + { url = "https://files.pythonhosted.org/packages/80/a3/d5974637f68e451f7fadf015cf3101d1cd7d8ba5027cffe0b9e3826ebe6b/ruff-0.15.15-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7aa77465b8ecaf1a27bea098d696f7fed5e1eccbd10b321b682d6de586ae5627", size = 11404550, upload-time = "2026-05-28T14:16:20.138Z" }, + { url = "https://files.pythonhosted.org/packages/fe/1c/e6e5e568f22be4fb05d6244234aba384c06b451252453b821e1a529263cf/ruff-0.15.15-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48decfa11d740de4889de623be1463308346312f2409a56e24aa280c86162dc4", size = 11382027, upload-time = "2026-05-28T14:16:46.615Z" }, + { url = "https://files.pythonhosted.org/packages/1d/01/170921b49fcd2e8858825593f91cf7146c3e40a5c3e6df763e4bb0484dde/ruff-0.15.15-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:a5015088452ca0081387063649ec67f06d3d1d6b8b936a1f836b5e9657ecd48c", size = 11366041, upload-time = "2026-05-28T14:16:26.247Z" }, + { url = "https://files.pythonhosted.org/packages/87/54/a7bad711d7de93254e15e06a4c375b89a03d18de45d3e5dcc86a4472fb1a/ruff-0.15.15-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f5294aab6356c81600fcdea3a62bb1b924dfd5e91767c12318d3f68f86af57cd", size = 10741795, upload-time = "2026-05-28T14:16:17.11Z" }, + { url = "https://files.pythonhosted.org/packages/c9/31/38c075963668f8b41c6914ee0f6f318727fbe30ab9145cb29e6df464c5fa/ruff-0.15.15-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:db5bd4d802415cca656dc1616070b725952d6ae95eb5d4831e49fbd94a38f75f", size = 10511117, upload-time = "2026-05-28T14:16:31.767Z" }, + { url = "https://files.pythonhosted.org/packages/9d/96/6ff689e1f7e375d1d97075eca022f74c2bab59554a432fe4d2e6f091986a/ruff-0.15.15-py3-none-musllinux_1_2_i686.whl", hash = "sha256:587a6278ed42059191c1a466e490bd7930fb50bd2e255398bc29616c895a61cb", size = 10994867, upload-time = "2026-05-28T14:16:35.149Z" }, + { url = "https://files.pythonhosted.org/packages/c3/c2/5dce0ab9f92a8d534fa62b9bf9caca3eddb8c1a81b616f5e195ada4f0d6e/ruff-0.15.15-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:df0c1c084f5f4be9812f61518a45c440d3c30d69ce4bf6c5270e66d38338f02a", size = 11482101, upload-time = "2026-05-28T14:16:49.598Z" }, + { url = "https://files.pythonhosted.org/packages/b1/c0/1003b60edd697c649faf61f1a34094b1abb38fb3d1181e3f895781250a08/ruff-0.15.15-py3-none-win32.whl", hash = "sha256:29428ea79694afbe756d45fd59b36f22b6b020dc0443cf7de0173046236964b9", size = 10716774, upload-time = "2026-05-28T14:16:52.337Z" }, + { url = "https://files.pythonhosted.org/packages/02/a8/1269eddd6945a06c23f055ef7848886e37cf9d6a8bebb386a3115f01470c/ruff-0.15.15-py3-none-win_amd64.whl", hash = "sha256:8df0323902e15e24bc4bf246da830573d3cf3352bd0b9a164eab335d111ff4a4", size = 11868463, upload-time = "2026-05-28T14:16:11.333Z" }, + { url = "https://files.pythonhosted.org/packages/4e/b2/920464c907b191e37469d477a1aa8bc048b8f36c4c1610dfa4ab87b39e18/ruff-0.15.15-py3-none-win_arm64.whl", hash = "sha256:3c8ceca6792f38196b8f589bc92eccd03eef286602da92e5dc05cc42ef6441b7", size = 11138498, upload-time = "2026-05-28T14:16:38.425Z" }, ] [[package]] @@ -2769,6 +2832,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ea/f1/5e9b3ba5c7aa7ebfaf269657e728067d16a7c99401c7973ddf5f0cf121bd/shapely-2.1.1-cp313-cp313t-win_amd64.whl", hash = "sha256:8cb8f17c377260452e9d7720eeaf59082c5f8ea48cf104524d953e5d36d4bdb7", size = 1723061, upload-time = "2025-05-19T11:04:40.082Z" }, ] +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, +] + [[package]] name = "simple-websocket" version = "1.1.0" @@ -2847,28 +2919,29 @@ wheels = [ [[package]] name = "tokenizers" -version = "0.22.2" +version = "0.23.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "huggingface-hub" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/73/6f/f80cfef4a312e1fb34baf7d85c72d4411afde10978d4657f8cdd811d3ccc/tokenizers-0.22.2.tar.gz", hash = "sha256:473b83b915e547aa366d1eee11806deaf419e17be16310ac0a14077f1e28f917", size = 372115, upload-time = "2026-01-05T10:45:15.988Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c1/60/21f715d9faba5f5407ff759472ade058ec4a507ad62bcea47cb847239a73/tokenizers-0.23.1.tar.gz", hash = "sha256:1feeeadf865a7915adc25445dea30e9933e593c31bb96c277cee36de227c8bfa", size = 365748, upload-time = "2026-04-27T14:43:25.606Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/92/97/5dbfabf04c7e348e655e907ed27913e03db0923abb5dfdd120d7b25630e1/tokenizers-0.22.2-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:544dd704ae7238755d790de45ba8da072e9af3eea688f698b137915ae959281c", size = 3100275, upload-time = "2026-01-05T10:41:02.158Z" }, - { url = "https://files.pythonhosted.org/packages/2e/47/174dca0502ef88b28f1c9e06b73ce33500eedfac7a7692108aec220464e7/tokenizers-0.22.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:1e418a55456beedca4621dbab65a318981467a2b188e982a23e117f115ce5001", size = 2981472, upload-time = "2026-01-05T10:41:00.276Z" }, - { url = "https://files.pythonhosted.org/packages/d6/84/7990e799f1309a8b87af6b948f31edaa12a3ed22d11b352eaf4f4b2e5753/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2249487018adec45d6e3554c71d46eb39fa8ea67156c640f7513eb26f318cec7", size = 3290736, upload-time = "2026-01-05T10:40:32.165Z" }, - { url = "https://files.pythonhosted.org/packages/78/59/09d0d9ba94dcd5f4f1368d4858d24546b4bdc0231c2354aa31d6199f0399/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25b85325d0815e86e0bac263506dd114578953b7b53d7de09a6485e4a160a7dd", size = 3168835, upload-time = "2026-01-05T10:40:38.847Z" }, - { url = "https://files.pythonhosted.org/packages/47/50/b3ebb4243e7160bda8d34b731e54dd8ab8b133e50775872e7a434e524c28/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfb88f22a209ff7b40a576d5324bf8286b519d7358663db21d6246fb17eea2d5", size = 3521673, upload-time = "2026-01-05T10:40:56.614Z" }, - { url = "https://files.pythonhosted.org/packages/e0/fa/89f4cb9e08df770b57adb96f8cbb7e22695a4cb6c2bd5f0c4f0ebcf33b66/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c774b1276f71e1ef716e5486f21e76333464f47bece56bbd554485982a9e03e", size = 3724818, upload-time = "2026-01-05T10:40:44.507Z" }, - { url = "https://files.pythonhosted.org/packages/64/04/ca2363f0bfbe3b3d36e95bf67e56a4c88c8e3362b658e616d1ac185d47f2/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df6c4265b289083bf710dff49bc51ef252f9d5be33a45ee2bed151114a56207b", size = 3379195, upload-time = "2026-01-05T10:40:51.139Z" }, - { url = "https://files.pythonhosted.org/packages/2e/76/932be4b50ef6ccedf9d3c6639b056a967a86258c6d9200643f01269211ca/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:369cc9fc8cc10cb24143873a0d95438bb8ee257bb80c71989e3ee290e8d72c67", size = 3274982, upload-time = "2026-01-05T10:40:58.331Z" }, - { url = "https://files.pythonhosted.org/packages/1d/28/5f9f5a4cc211b69e89420980e483831bcc29dade307955cc9dc858a40f01/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:29c30b83d8dcd061078b05ae0cb94d3c710555fbb44861139f9f83dcca3dc3e4", size = 9478245, upload-time = "2026-01-05T10:41:04.053Z" }, - { url = "https://files.pythonhosted.org/packages/6c/fb/66e2da4704d6aadebf8cb39f1d6d1957df667ab24cff2326b77cda0dcb85/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:37ae80a28c1d3265bb1f22464c856bd23c02a05bb211e56d0c5301a435be6c1a", size = 9560069, upload-time = "2026-01-05T10:45:10.673Z" }, - { url = "https://files.pythonhosted.org/packages/16/04/fed398b05caa87ce9b1a1bb5166645e38196081b225059a6edaff6440fac/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:791135ee325f2336f498590eb2f11dc5c295232f288e75c99a36c5dbce63088a", size = 9899263, upload-time = "2026-01-05T10:45:12.559Z" }, - { url = "https://files.pythonhosted.org/packages/05/a1/d62dfe7376beaaf1394917e0f8e93ee5f67fea8fcf4107501db35996586b/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:38337540fbbddff8e999d59970f3c6f35a82de10053206a7562f1ea02d046fa5", size = 10033429, upload-time = "2026-01-05T10:45:14.333Z" }, - { url = "https://files.pythonhosted.org/packages/fd/18/a545c4ea42af3df6effd7d13d250ba77a0a86fb20393143bbb9a92e434d4/tokenizers-0.22.2-cp39-abi3-win32.whl", hash = "sha256:a6bf3f88c554a2b653af81f3204491c818ae2ac6fbc09e76ef4773351292bc92", size = 2502363, upload-time = "2026-01-05T10:45:20.593Z" }, - { url = "https://files.pythonhosted.org/packages/65/71/0670843133a43d43070abeb1949abfdef12a86d490bea9cd9e18e37c5ff7/tokenizers-0.22.2-cp39-abi3-win_amd64.whl", hash = "sha256:c9ea31edff2968b44a88f97d784c2f16dc0729b8b143ed004699ebca91f05c48", size = 2747786, upload-time = "2026-01-05T10:45:18.411Z" }, - { url = "https://files.pythonhosted.org/packages/72/f4/0de46cfa12cdcbcd464cc59fde36912af405696f687e53a091fb432f694c/tokenizers-0.22.2-cp39-abi3-win_arm64.whl", hash = "sha256:9ce725d22864a1e965217204946f830c37876eee3b2ba6fc6255e8e903d5fcbc", size = 2612133, upload-time = "2026-01-05T10:45:17.232Z" }, + { url = "https://files.pythonhosted.org/packages/87/39/b87a87d5bb9470610b80a2d31df42fcffeaf35118b8b97952b2aff598cc7/tokenizers-0.23.1-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:e03d6ffcbe0d56ee9c1ccd070e70a13fa750727c0277e138152acbc0252c2224", size = 3146732, upload-time = "2026-04-27T14:43:15.427Z" }, + { url = "https://files.pythonhosted.org/packages/e2/6a/068ed9f6e444c9d7e9d55ce134181325700f3d7f30410721bdc8f848d727/tokenizers-0.23.1-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:e0948bbb1ac1d7cdfc9fb6d62c596e3b7550036ad60ecd654a66ad273326324e", size = 3054954, upload-time = "2026-04-27T14:43:13.745Z" }, + { url = "https://files.pythonhosted.org/packages/6c/36/e006edf031154cba92b8416057d92c3abe3635e4c4b0aa0b5b9bb39dde70/tokenizers-0.23.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bf13402aff9bc533c89cb849ec3b412dc3fbeacc9744840e423d7bf3f7dc0e3", size = 3374081, upload-time = "2026-04-27T14:43:01.241Z" }, + { url = "https://files.pythonhosted.org/packages/a2/ef/7735d226f9c7f874a6bee5e3f27fb25ecabdf207d37b8cf45286d0795893/tokenizers-0.23.1-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f836ca703b89ae07919a309f9651f7a88fd5a33d5f718ba5ad0870ec0256bad6", size = 3247641, upload-time = "2026-04-27T14:43:03.856Z" }, + { url = "https://files.pythonhosted.org/packages/b9/d9/24827036f6e21297bfffda0768e58eb6096a4f411e932964a01707857931/tokenizers-0.23.1-cp310-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae848657742035523fdf261773630cb819a26995fcd3d9ecae0c1daf6e5a4959", size = 3585624, upload-time = "2026-04-27T14:43:10.664Z" }, + { url = "https://files.pythonhosted.org/packages/0c/9a/22f3582b3a4f49358293a5206e25317621ee4526bfe9cdaa0f07a12e770e/tokenizers-0.23.1-cp310-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:53b09e85775d5187941e7bab30e941b4134ab4a7dd8c68e783d231fb7ca27c51", size = 3844062, upload-time = "2026-04-27T14:43:05.643Z" }, + { url = "https://files.pythonhosted.org/packages/7e/65/b8f8814eef95800f20721384136d9a1d22241d50b2874357cb70542c392f/tokenizers-0.23.1-cp310-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea5a0ce170074329faaa8ea3f6400ecde604b6678192688533af80980daae71a", size = 3460098, upload-time = "2026-04-27T14:43:08.854Z" }, + { url = "https://files.pythonhosted.org/packages/0d/d5/1353e5f677ec27c2494fb6a6725e82d56c985f53e90ec511369e7e4f02c6/tokenizers-0.23.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5075b405006415ea148a992d093699c66eb01952bf59f4d5727089a98bda45a4", size = 3346235, upload-time = "2026-04-27T14:43:12.377Z" }, + { url = "https://files.pythonhosted.org/packages/71/89/39b6b8fc073fb6d413d0147aa333dc7eff7be65639ac9d19930a0b21bf33/tokenizers-0.23.1-cp310-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:56f3a77de629917652f876294dc9fe6bad4a0c43bc229dc72e59bb23a0f4729a", size = 3426398, upload-time = "2026-04-27T14:43:07.264Z" }, + { url = "https://files.pythonhosted.org/packages/0f/80/127c854da64827e5b79264ce524993a90dddcb320e5cd42412c5c02f9e8a/tokenizers-0.23.1-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9d10a6d957ef01896dc274e890eee27d41bd0e74ef31e60616f0fc311345184e", size = 9823279, upload-time = "2026-04-27T14:43:17.222Z" }, + { url = "https://files.pythonhosted.org/packages/fe/ba/44c2502feb1a058f096ddfb4e0996ef3225a01a388e1a9b094e91689fe93/tokenizers-0.23.1-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:1974288a609c343774f1b897c8b482c791ab17b75ab5c8c2b1737565c1d82288", size = 9644986, upload-time = "2026-04-27T14:43:19.45Z" }, + { url = "https://files.pythonhosted.org/packages/9e/c1/464019a9fb059870bfe4eebb4ba12208f3042035e258bf5e782906bd3847/tokenizers-0.23.1-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:120468fb4c24faf0543c835a4fabafa4deb3f20a035c9b6e83d0b553a97615d4", size = 9976181, upload-time = "2026-04-27T14:43:21.463Z" }, + { url = "https://files.pythonhosted.org/packages/79/94/3ac1432bda31626071e9b6a12709b97ae05131c804b94c8f3ac622c5da32/tokenizers-0.23.1-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e3d8f40ea6268047de7046906326abed5134f27d4e8447b23763afe5808c8a96", size = 10113853, upload-time = "2026-04-27T14:43:23.617Z" }, + { url = "https://files.pythonhosted.org/packages/6a/dd/631b21433c771b1382535326f0eca80b9c9cee2e64961dd993bc9ac4669e/tokenizers-0.23.1-cp310-abi3-win32.whl", hash = "sha256:93120a930b919416da7cd10a2f606ac9919cc69cacae7980fa2140e277660948", size = 2536263, upload-time = "2026-04-27T14:43:29.888Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/2553f72aaf65a2797d4229e37fa7fbe38ffbf3e32912d31bdd78b3323e59/tokenizers-0.23.1-cp310-abi3-win_amd64.whl", hash = "sha256:e7bfaf995c1bdbbd21d13539decb6650967013759318627d85daeb7881af16b7", size = 2798223, upload-time = "2026-04-27T14:43:28.51Z" }, + { url = "https://files.pythonhosted.org/packages/cd/2b/2be299bab55fc595e3d38567edb1a87f86e594842968fa9515a07bdcf422/tokenizers-0.23.1-cp310-abi3-win_arm64.whl", hash = "sha256:a26197957d8e4425dfba746315f3c425ea00cfa8367c5fbc4ec73447893dcea9", size = 2664127, upload-time = "2026-04-27T14:43:26.949Z" }, ] [[package]] @@ -2933,42 +3006,57 @@ wheels = [ ] [[package]] -name = "types-pyyaml" -version = "6.0.12.20260408" +name = "typer" +version = "0.25.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/74/73/b759b1e413c31034cc01ecdfb96b38115d0ab4db55a752a3929f0cd449fd/types_pyyaml-6.0.12.20260408.tar.gz", hash = "sha256:92a73f2b8d7f39ef392a38131f76b970f8c66e4c42b3125ae872b7c93b556307", size = 17735, upload-time = "2026-04-08T04:30:50.974Z" } +dependencies = [ + { name = "annotated-doc" }, + { name = "click" }, + { name = "rich" }, + { name = "shellingham" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e4/51/9aed62104cea109b820bbd6c14245af756112017d309da813ef107d42e7e/typer-0.25.1.tar.gz", hash = "sha256:9616eb8853a09ffeabab1698952f33c6f29ffdbceb4eaeecf571880e8d7664cc", size = 122276, upload-time = "2026-04-30T19:32:16.964Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1c/f0/c391068b86abb708882c6d75a08cd7d25b2c7227dab527b3a3685a3c635b/types_pyyaml-6.0.12.20260408-py3-none-any.whl", hash = "sha256:fbc42037d12159d9c801ebfcc79ebd28335a7c13b08a4cfbc6916df78fee9384", size = 20339, upload-time = "2026-04-08T04:30:50.113Z" }, + { url = "https://files.pythonhosted.org/packages/3f/f9/2b3ff4e56e5fa7debfaf9eb135d0da96f3e9a1d5b27222223c7296336e5f/typer-0.25.1-py3-none-any.whl", hash = "sha256:75caa44ed46a03fb2dab8808753ffacdbfea88495e74c85a28c5eefcf5f39c89", size = 58409, upload-time = "2026-04-30T19:32:18.271Z" }, +] + +[[package]] +name = "types-pyyaml" +version = "6.0.12.20260518" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b8/83/4a1afc3fbfcf5b8d46fc390cd95ed6b0dc9010a265f4e9f46314efffa37a/types_pyyaml-6.0.12.20260518.tar.gz", hash = "sha256:d917f83fb38462550338c1297faedd860b3ec83912b96b1e3d73255f7473e466", size = 17850, upload-time = "2026-05-18T06:01:58.675Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/a2/c01db32be2ae7d6a1689972f3c492b149ee4e164b12fdfd9f64b50888215/types_pyyaml-6.0.12.20260518-py3-none-any.whl", hash = "sha256:d2150f75a231c9fe9c7463bd29487d93e60bac90400287351384bc2284eba7cd", size = 20312, upload-time = "2026-05-18T06:01:57.368Z" }, ] [[package]] name = "types-requests" -version = "2.33.0.20260408" +version = "2.33.0.20260518" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/69/6a/749dc53a54a3f35842c1f8197b3ca6b54af6d7458a1bfc75f6629b6da666/types_requests-2.33.0.20260408.tar.gz", hash = "sha256:95b9a86376807a216b2fb412b47617b202091c3ea7c078f47cc358d5528ccb7b", size = 23882, upload-time = "2026-04-08T04:34:49.33Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/01/c5a19253fe1ac159159ddf9a3a07cec8bb5e486ec4d9002ad2821da0e5d2/types_requests-2.33.0.20260518.tar.gz", hash = "sha256:df7bd3bfe0ca8402dfb841e7d9be714bb5578203283d66d7dc4ef69343449a5e", size = 24752, upload-time = "2026-05-18T06:07:37.966Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/90/b8/78fd6c037de4788c040fdd323b3369804400351b7827473920f6c1d03c10/types_requests-2.33.0.20260408-py3-none-any.whl", hash = "sha256:81f31d5ea4acb39f03be7bc8bed569ba6d5a9c5d97e89f45ac43d819b68ca50f", size = 20739, upload-time = "2026-04-08T04:34:48.325Z" }, + { url = "https://files.pythonhosted.org/packages/1c/bc/b139710a3b6018f7fb2b9508b35c8af564e61bf2bf4fa619d088f3e16f85/types_requests-2.33.0.20260518-py3-none-any.whl", hash = "sha256:626d697d1adaaff76e2044dc8c5c051d8f21abc157bdfe204a75558076fe0bf0", size = 21391, upload-time = "2026-05-18T06:07:37.044Z" }, ] [[package]] name = "types-setuptools" -version = "82.0.0.20260408" +version = "82.0.0.20260518" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c3/12/3464b410c50420dd4674fa5fe9d3880711c1dbe1a06f5fe4960ee9067b9e/types_setuptools-82.0.0.20260408.tar.gz", hash = "sha256:036c68caf7e672a699f5ebbf914708d40644c14e05298bc49f7272be91cf43d3", size = 44861, upload-time = "2026-04-08T04:29:33.292Z" } +sdist = { url = "https://files.pythonhosted.org/packages/38/bc/73c2c27e047e42f114ac50fb3bdef986c56cbdb68096f8690eeafb839a93/types_setuptools-82.0.0.20260518.tar.gz", hash = "sha256:3b743cfe63d0981ea4c15b90710fc1ed41e3464a537d51e705be514e891c1d07", size = 44999, upload-time = "2026-05-18T06:02:55.642Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3d/e1/46a4fc3ef03aabf5d18bac9df5cf37c6b02c3bddf3e05c3533f4b4588331/types_setuptools-82.0.0.20260408-py3-none-any.whl", hash = "sha256:ece0a215cdfa6463a65fd6f68bd940f39e455729300ddfe61cab1147ed1d2462", size = 68428, upload-time = "2026-04-08T04:29:32.175Z" }, + { url = "https://files.pythonhosted.org/packages/32/8f/d5e2d493f09a7a98c95619edda1cb37cee377626c0a869d53274c26f2858/types_setuptools-82.0.0.20260518-py3-none-any.whl", hash = "sha256:31c04a62b57a653a5021caf191be0f10f70df890f813b51f02bab3969d300f20", size = 68444, upload-time = "2026-05-18T06:02:54.582Z" }, ] [[package]] name = "types-simplejson" -version = "3.20.0.20260408" +version = "3.20.0.20260518" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9f/36/e319fd0f6d906dbf7c2c03eef17db77ef461197a75b253fccd9c7c695d3e/types_simplejson-3.20.0.20260408.tar.gz", hash = "sha256:0b0e1bf61e70f81dfe6ef4c2b9c02e39403848c0652df334e7a430c3a26c06b3", size = 10693, upload-time = "2026-04-08T04:28:07.8Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/e4/f8ecdc3529688ad058b0496f67a1bdfbd2576c3669e1c478b6347b12cb7e/types_simplejson-3.20.0.20260518.tar.gz", hash = "sha256:298c5b2fc2df3eb128d2077abd5bd1f5b0419dcf72fdf0551b2af2f03b1ff2b3", size = 10777, upload-time = "2026-05-18T06:04:39.57Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/22/c0/01a5a4c3948c2269cf9d727e5e66a8b404e03beb4f9522680a3f71097011/types_simplejson-3.20.0.20260408-py3-none-any.whl", hash = "sha256:f9e542199cb159ed34ad54b6ceb3dc9af890c256b810ad1bd7c69c61db7d2236", size = 10415, upload-time = "2026-04-08T04:28:06.984Z" }, + { url = "https://files.pythonhosted.org/packages/a6/41/16937326596bec6283a51cc901c701a964019569b861ac5f88128bc98b43/types_simplejson-3.20.0.20260518-py3-none-any.whl", hash = "sha256:f4792dbc1ab049fcaee5d99ca950bd6468ea462fe79af25fe8ca08b6bee313ac", size = 10421, upload-time = "2026-05-18T06:04:38.706Z" }, ] [[package]] @@ -3012,15 +3100,15 @@ wheels = [ [[package]] name = "uvicorn" -version = "0.44.0" +version = "0.48.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, { name = "h11" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5e/da/6eee1ff8b6cbeed47eeb5229749168e81eb4b7b999a1a15a7176e51410c9/uvicorn-0.44.0.tar.gz", hash = "sha256:6c942071b68f07e178264b9152f1f16dfac5da85880c4ce06366a96d70d4f31e", size = 86947, upload-time = "2026-04-06T09:23:22.826Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e6/bf/f6544ba992ddb9a6077343a576f9844f7f8f06ab819aefd00206e9255f18/uvicorn-0.48.0.tar.gz", hash = "sha256:a5504207195d08c2511bf9125ede5ac4a4b71725d519e758d01dcf0bc2d31c37", size = 91074, upload-time = "2026-05-24T12:08:41.925Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/23/a5bbd9600dd607411fa644c06ff4951bec3a4d82c4b852374024359c19c0/uvicorn-0.44.0-py3-none-any.whl", hash = "sha256:ce937c99a2cc70279556967274414c087888e8cec9f9c94644dfca11bd3ced89", size = 69425, upload-time = "2026-04-06T09:23:21.524Z" }, + { url = "https://files.pythonhosted.org/packages/01/be/72532be3da7acc5fdfbccdb95215cd04f995a0886532a5b423f929cda4cc/uvicorn-0.48.0-py3-none-any.whl", hash = "sha256:48097851328b87ec36117d3d575234519eb58c2b22d79666e9bbc6c49a761dad", size = 71410, upload-time = "2026-05-24T12:08:40.258Z" }, ] [package.optional-dependencies] diff --git a/misc/release/pump-version.sh b/misc/release/pump-version.sh index 6be0ddebb9..39a3364723 100755 --- a/misc/release/pump-version.sh +++ b/misc/release/pump-version.sh @@ -64,16 +64,13 @@ if [ "$CURRENT_SERVER" != "$NEXT_SERVER" ]; then pnpm version "$NEXT_SERVER" --no-git-tag-version pnpm version "$NEXT_SERVER" --no-git-tag-version --prefix server - pnpm version "$NEXT_SERVER" --no-git-tag-version --prefix i18n - pnpm version "$NEXT_SERVER" --no-git-tag-version --prefix cli + pnpm version "$NEXT_SERVER" --no-git-tag-version --prefix packages/cli pnpm version "$NEXT_SERVER" --no-git-tag-version --prefix web pnpm version "$NEXT_SERVER" --no-git-tag-version --prefix e2e - pnpm version "$NEXT_SERVER" --no-git-tag-version --prefix open-api/typescript-sdk + pnpm version "$NEXT_SERVER" --no-git-tag-version --prefix packages/sdk # copy version to open-api spec - pnpm install --frozen-lockfile --prefix server - pnpm --prefix server run build - ( cd ./open-api && bash ./bin/generate-open-api.sh ) + mise run //:open-api uv version --directory machine-learning "$NEXT_SERVER" diff --git a/mise.lock b/mise.lock new file mode 100644 index 0000000000..df7819caa1 --- /dev/null +++ b/mise.lock @@ -0,0 +1,289 @@ +# @generated - this file is auto-generated by `mise lock` https://mise.en.dev/dev-tools/mise-lock.html + +[[tools."github:extism/cli"]] +version = "1.6.3" +backend = "github:extism/cli" + +[tools."github:extism/cli"."platforms.linux-arm64"] +checksum = "sha256:d92f830c9be39637569feacb04e9750c28848df6d9a219db94152a9b4eb9452b" +url = "https://github.com/extism/cli/releases/download/v1.6.3/extism-v1.6.3-linux-arm64.tar.gz" +url_api = "https://api.github.com/repos/extism/cli/releases/assets/275694030" + +[tools."github:extism/cli"."platforms.linux-arm64-musl"] +checksum = "sha256:d92f830c9be39637569feacb04e9750c28848df6d9a219db94152a9b4eb9452b" +url = "https://github.com/extism/cli/releases/download/v1.6.3/extism-v1.6.3-linux-arm64.tar.gz" +url_api = "https://api.github.com/repos/extism/cli/releases/assets/275694030" + +[tools."github:extism/cli"."platforms.linux-x64"] +checksum = "sha256:34e7ae9bfded6e2c32dee83f70a4e50d34f9d3e80d1762b09625fe82e214d02d" +url = "https://github.com/extism/cli/releases/download/v1.6.3/extism-v1.6.3-linux-amd64.tar.gz" +url_api = "https://api.github.com/repos/extism/cli/releases/assets/275694025" + +[tools."github:extism/cli"."platforms.linux-x64-musl"] +checksum = "sha256:34e7ae9bfded6e2c32dee83f70a4e50d34f9d3e80d1762b09625fe82e214d02d" +url = "https://github.com/extism/cli/releases/download/v1.6.3/extism-v1.6.3-linux-amd64.tar.gz" +url_api = "https://api.github.com/repos/extism/cli/releases/assets/275694025" + +[tools."github:extism/cli"."platforms.macos-arm64"] +checksum = "sha256:b4ddbc575b5ac000115247f781723f9b9f284ed87b29c600539d72161b5b29fc" +url = "https://github.com/extism/cli/releases/download/v1.6.3/extism-v1.6.3-darwin-arm64.tar.gz" +url_api = "https://api.github.com/repos/extism/cli/releases/assets/275694029" + +[tools."github:extism/cli"."platforms.macos-x64"] +checksum = "sha256:9a2f71b6e6009685a622cc3084e52d2a1a8e23c98d29ffa72e666e9dc699855f" +url = "https://github.com/extism/cli/releases/download/v1.6.3/extism-v1.6.3-darwin-amd64.tar.gz" +url_api = "https://api.github.com/repos/extism/cli/releases/assets/275694026" + +[tools."github:extism/cli"."platforms.windows-x64"] +checksum = "sha256:47e4ed2782445b2b08a4d1ac127211588f8b4d1fc25fd6481d4cb65151b5213c" +url = "https://github.com/extism/cli/releases/download/v1.6.3/extism-v1.6.3-windows-amd64.zip" +url_api = "https://api.github.com/repos/extism/cli/releases/assets/275694035" + +[[tools."github:extism/js-pdk"]] +version = "1.6.0" +backend = "github:extism/js-pdk" + +[tools."github:extism/js-pdk"."platforms.linux-arm64"] +checksum = "sha256:15a186250e68d6bff4ec839fff275d45a90e383a69209dcc1239eb9e3aee6e1b" +url = "https://github.com/extism/js-pdk/releases/download/v1.6.0/extism-js-aarch64-linux-v1.6.0.gz" +url_api = "https://api.github.com/repos/extism/js-pdk/releases/assets/353223214" + +[tools."github:extism/js-pdk"."platforms.linux-arm64-musl"] +checksum = "sha256:15a186250e68d6bff4ec839fff275d45a90e383a69209dcc1239eb9e3aee6e1b" +url = "https://github.com/extism/js-pdk/releases/download/v1.6.0/extism-js-aarch64-linux-v1.6.0.gz" +url_api = "https://api.github.com/repos/extism/js-pdk/releases/assets/353223214" + +[tools."github:extism/js-pdk"."platforms.linux-x64"] +checksum = "sha256:4ded271ccf465031ccd0dc35e7a140e134d7f30721671cc4a8e1ff805d4aad68" +url = "https://github.com/extism/js-pdk/releases/download/v1.6.0/extism-js-x86_64-linux-v1.6.0.gz" +url_api = "https://api.github.com/repos/extism/js-pdk/releases/assets/353223119" + +[tools."github:extism/js-pdk"."platforms.linux-x64-musl"] +checksum = "sha256:4ded271ccf465031ccd0dc35e7a140e134d7f30721671cc4a8e1ff805d4aad68" +url = "https://github.com/extism/js-pdk/releases/download/v1.6.0/extism-js-x86_64-linux-v1.6.0.gz" +url_api = "https://api.github.com/repos/extism/js-pdk/releases/assets/353223119" + +[tools."github:extism/js-pdk"."platforms.macos-arm64"] +checksum = "sha256:548e25bda3971a07c32d78a249135cf8cb7b3eede101e878e06e53e01ac2e0ce" +url = "https://github.com/extism/js-pdk/releases/download/v1.6.0/extism-js-aarch64-macos-v1.6.0.gz" +url_api = "https://api.github.com/repos/extism/js-pdk/releases/assets/353223215" + +[tools."github:extism/js-pdk"."platforms.macos-x64"] +checksum = "sha256:d85a875c2a071f0c29fe572764c52c3a499f157ab7f9efac8939a4364390e29b" +url = "https://github.com/extism/js-pdk/releases/download/v1.6.0/extism-js-x86_64-macos-v1.6.0.gz" +url_api = "https://api.github.com/repos/extism/js-pdk/releases/assets/353223239" + +[tools."github:extism/js-pdk"."platforms.windows-x64"] +checksum = "sha256:97b7b746141e4777e1ca2b76febdeb16dc9d314ff6a4257df05a476b67228acc" +url = "https://github.com/extism/js-pdk/releases/download/v1.6.0/extism-js-x86_64-windows-v1.6.0.gz" +url_api = "https://api.github.com/repos/extism/js-pdk/releases/assets/353224133" + +[[tools."github:jellyfin/jellyfin-ffmpeg"]] +version = "7.1.3-6" +backend = "github:jellyfin/jellyfin-ffmpeg" + +[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.linux-arm64"] +checksum = "sha256:bea03c670e8cc5bfe9edc0c5d624d4735421610cef5e808db93e7d8596952886" +url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_linuxarm64-gpl.tar.xz" +url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/409048876" + +[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.linux-arm64-musl"] +checksum = "sha256:bea03c670e8cc5bfe9edc0c5d624d4735421610cef5e808db93e7d8596952886" +url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_linuxarm64-gpl.tar.xz" +url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/409048876" + +[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.linux-x64"] +checksum = "sha256:39e99a7927468a6abec5f65d00f55010e8ff2ae3c2605294f179c94f6ae21af2" +url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_linux64-gpl.tar.xz" +url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/409048879" + +[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.linux-x64-musl"] +checksum = "sha256:39e99a7927468a6abec5f65d00f55010e8ff2ae3c2605294f179c94f6ae21af2" +url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_linux64-gpl.tar.xz" +url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/409048879" + +[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.macos-arm64"] +checksum = "sha256:e024d5e78d5414e75f0181036cd21373fafb9270c72894dfd7dbda2572439820" +url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_macarm64-gpl.tar.xz" +url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/408995838" + +[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.macos-x64"] +checksum = "sha256:066ede9774aaae97a18098aaeea8b7e0d286653eb8618f640476e99c59a536c2" +url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_mac64-gpl.tar.xz" +url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/408995889" + +[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.windows-x64"] +checksum = "sha256:7b7168149689610296f3a187c717056ce0786cc125a31caf28056737e9ba1cc1" +url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_win64-clang-gpl.zip" +url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/409036094" + +[[tools."github:webassembly/binaryen"]] +version = "version_124" +backend = "github:webassembly/binaryen" + +[tools."github:webassembly/binaryen"."platforms.linux-arm64"] +checksum = "sha256:6291bd9a57d8e046f3bc099a4db386c147433a87f71c783a901c5b1792e38de3" +url = "https://github.com/WebAssembly/binaryen/releases/download/version_124/binaryen-version_124-aarch64-linux.tar.gz" +url_api = "https://api.github.com/repos/WebAssembly/binaryen/releases/assets/288927659" + +[tools."github:webassembly/binaryen"."platforms.linux-arm64-musl"] +checksum = "sha256:6291bd9a57d8e046f3bc099a4db386c147433a87f71c783a901c5b1792e38de3" +url = "https://github.com/WebAssembly/binaryen/releases/download/version_124/binaryen-version_124-aarch64-linux.tar.gz" +url_api = "https://api.github.com/repos/WebAssembly/binaryen/releases/assets/288927659" + +[tools."github:webassembly/binaryen"."platforms.linux-x64"] +checksum = "sha256:0290c3779fedf592b8da0ded3032ff55c41a2b7bfa2d6bf7b7bac6f0e6e28963" +url = "https://github.com/WebAssembly/binaryen/releases/download/version_124/binaryen-version_124-x86_64-linux.tar.gz" +url_api = "https://api.github.com/repos/WebAssembly/binaryen/releases/assets/288926769" + +[tools."github:webassembly/binaryen"."platforms.linux-x64-musl"] +checksum = "sha256:0290c3779fedf592b8da0ded3032ff55c41a2b7bfa2d6bf7b7bac6f0e6e28963" +url = "https://github.com/WebAssembly/binaryen/releases/download/version_124/binaryen-version_124-x86_64-linux.tar.gz" +url_api = "https://api.github.com/repos/WebAssembly/binaryen/releases/assets/288926769" + +[tools."github:webassembly/binaryen"."platforms.macos-arm64"] +checksum = "sha256:86a2c960ff62c6d2ea6009d1f89745c22c70100d394a095eab45eb941bdaa24c" +url = "https://github.com/WebAssembly/binaryen/releases/download/version_124/binaryen-version_124-arm64-macos.tar.gz" +url_api = "https://api.github.com/repos/WebAssembly/binaryen/releases/assets/288926134" + +[tools."github:webassembly/binaryen"."platforms.macos-x64"] +checksum = "sha256:b389bb0731758d86c3cb266d01d28a12725c23bd3cabc3df34faa162af0887e9" +url = "https://github.com/WebAssembly/binaryen/releases/download/version_124/binaryen-version_124-x86_64-macos.tar.gz" +url_api = "https://api.github.com/repos/WebAssembly/binaryen/releases/assets/288926135" + +[tools."github:webassembly/binaryen"."platforms.windows-x64"] +checksum = "sha256:b5e1d2a1ad3c03229ddc89823848f4a1c11f9c6402a51fa26f0aaa5f1d7a2203" +url = "https://github.com/WebAssembly/binaryen/releases/download/version_124/binaryen-version_124-x86_64-windows.tar.gz" +url_api = "https://api.github.com/repos/WebAssembly/binaryen/releases/assets/288925833" + +[[tools.node]] +version = "24.15.0" +backend = "core:node" + +[tools.node."platforms.linux-arm64"] +checksum = "sha256:73afc234d558c24919875f51c2d1ea002a2ada4ea6f83601a383869fefa64eed" +url = "https://nodejs.org/dist/v24.15.0/node-v24.15.0-linux-arm64.tar.gz" + +[tools.node."platforms.linux-arm64-musl"] +checksum = "sha256:31e98aa960a067da91edffd5d93bc46657b5d2a8029612c359f5f2ac0060152a" +url = "https://unofficial-builds.nodejs.org/download/release/v24.15.0/node-v24.15.0-linux-arm64-musl.tar.gz" + +[tools.node."platforms.linux-x64"] +checksum = "sha256:44836872d9aec49f1e6b52a9a922872db9a2b02d235a616a5681b6a85fec8d89" +url = "https://nodejs.org/dist/v24.15.0/node-v24.15.0-linux-x64.tar.gz" + +[tools.node."platforms.linux-x64-musl"] +checksum = "sha256:f55af5bd489c5347b113ca6594cae00a54b30ba57ac5875324311bfc6f4762e3" +url = "https://unofficial-builds.nodejs.org/download/release/v24.15.0/node-v24.15.0-linux-x64-musl.tar.gz" + +[tools.node."platforms.macos-arm64"] +checksum = "sha256:372331b969779ab5d15b949884fc6eaf88d5afe87bde8ba881d6400b9100ffc4" +url = "https://nodejs.org/dist/v24.15.0/node-v24.15.0-darwin-arm64.tar.gz" + +[tools.node."platforms.macos-x64"] +checksum = "sha256:ffd5ee293467927f3ee731a553eb88fd1f48cf74eebc2d74a6babe4af228673b" +url = "https://nodejs.org/dist/v24.15.0/node-v24.15.0-darwin-x64.tar.gz" + +[tools.node."platforms.windows-x64"] +checksum = "sha256:cc5149eabd53779ce1e7bdc5401643622d0c7e6800ade18928a767e940bb0e62" +url = "https://nodejs.org/dist/v24.15.0/node-v24.15.0-win-x64.zip" + +[[tools."npm:oazapfts"]] +version = "7.5.0" +backend = "npm:oazapfts" + +[[tools.opentofu]] +version = "1.11.6" +backend = "aqua:opentofu/opentofu" + +[tools.opentofu."platforms.linux-arm64"] +checksum = "sha256:d4f2ab15776925864b049bb329d69682851de6f5204f256e9fa86d07a0308850" +url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_arm64.tar.gz" + +[tools.opentofu."platforms.linux-arm64-musl"] +checksum = "sha256:d4f2ab15776925864b049bb329d69682851de6f5204f256e9fa86d07a0308850" +url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_arm64.tar.gz" + +[tools.opentofu."platforms.linux-x64"] +checksum = "sha256:02800fafa2753a9f50c38483e2fdf5bc353fd62895eb9e25eec9a5145df3a69e" +url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_amd64.tar.gz" + +[tools.opentofu."platforms.linux-x64-musl"] +checksum = "sha256:02800fafa2753a9f50c38483e2fdf5bc353fd62895eb9e25eec9a5145df3a69e" +url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_amd64.tar.gz" + +[tools.opentofu."platforms.macos-arm64"] +checksum = "sha256:62d7fa8539e13b444827aa0a3b90c5972da5c47e8f8882d9dcf2e430e78840c1" +url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_darwin_arm64.tar.gz" + +[tools.opentofu."platforms.macos-x64"] +checksum = "sha256:1408cdef1c380f914565e6b4bb70794c6b163f195fcb233357f3d6c5745906b6" +url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_darwin_amd64.tar.gz" + +[tools.opentofu."platforms.windows-x64"] +checksum = "sha256:27323f70c875b8251bfd7e61a4cffc3ebff4e56ed1e611b955016f0c7077367e" +url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_windows_amd64.tar.gz" + +[[tools.pnpm]] +version = "10.33.4" +backend = "aqua:pnpm/pnpm" + +[tools.pnpm."platforms.linux-arm64"] +checksum = "sha256:d29649c7380b5cd522f574208fbd35335846686498f45004604d3f5b8658b5cb" +url = "https://github.com/pnpm/pnpm/releases/download/v10.33.4/pnpm-linux-arm64" + +[tools.pnpm."platforms.linux-arm64-musl"] +checksum = "sha256:d29649c7380b5cd522f574208fbd35335846686498f45004604d3f5b8658b5cb" +url = "https://github.com/pnpm/pnpm/releases/download/v10.33.4/pnpm-linux-arm64" + +[tools.pnpm."platforms.linux-x64"] +checksum = "sha256:ff1795595535a10d0dfe327303f3dd02377be141190b1f5756de68edde2cf813" +url = "https://github.com/pnpm/pnpm/releases/download/v10.33.4/pnpm-linux-x64" + +[tools.pnpm."platforms.linux-x64-musl"] +checksum = "sha256:ff1795595535a10d0dfe327303f3dd02377be141190b1f5756de68edde2cf813" +url = "https://github.com/pnpm/pnpm/releases/download/v10.33.4/pnpm-linux-x64" + +[tools.pnpm."platforms.macos-arm64"] +checksum = "sha256:7aae186a04e1ffaa0047d43cd07d68a98dec303304f28be52234ba955d26c671" +url = "https://github.com/pnpm/pnpm/releases/download/v10.33.4/pnpm-macos-arm64" + +[tools.pnpm."platforms.macos-x64"] +checksum = "sha256:3b0c97b9f794cdda293949a8ee0e0151ca08f512f4a832408386221c7c73eec6" +url = "https://github.com/pnpm/pnpm/releases/download/v10.33.4/pnpm-macos-x64" + +[tools.pnpm."platforms.windows-x64"] +checksum = "sha256:3268b2f29defe0dce8a3a26c0ef01488f0d4aa4872923173186ef618ab7d68ef" +url = "https://github.com/pnpm/pnpm/releases/download/v10.33.4/pnpm-win-x64.exe" + +[[tools.terragrunt]] +version = "1.0.3" +backend = "aqua:gruntwork-io/terragrunt" + +[tools.terragrunt."platforms.linux-arm64"] +checksum = "sha256:e5b60ab05b5214db694e6bc215d8124fb626e277cdb56b86f6147ae110d510fe" +url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_arm64.tar.gz" + +[tools.terragrunt."platforms.linux-arm64-musl"] +checksum = "sha256:e5b60ab05b5214db694e6bc215d8124fb626e277cdb56b86f6147ae110d510fe" +url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_arm64.tar.gz" + +[tools.terragrunt."platforms.linux-x64"] +checksum = "sha256:6d48049baf82e0bf9c804368dc85cbfeadc10955e33777e9e8de3e020b94b073" +url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_amd64.tar.gz" + +[tools.terragrunt."platforms.linux-x64-musl"] +checksum = "sha256:6d48049baf82e0bf9c804368dc85cbfeadc10955e33777e9e8de3e020b94b073" +url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_amd64.tar.gz" + +[tools.terragrunt."platforms.macos-arm64"] +checksum = "sha256:aacb5be2ca5475300cbce246dfbd8a45eb47510fbaa70fab8561c49ef5db03aa" +url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_darwin_arm64.tar.gz" + +[tools.terragrunt."platforms.macos-x64"] +checksum = "sha256:3133c2251e191aede8e3dd2a5b3aee2e91c5f08f88f117aee40eed9a24c8ef6b" +url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_darwin_amd64.tar.gz" + +[tools.terragrunt."platforms.windows-x64"] +checksum = "sha256:183b2745b4e04980a6bfa4450ff81956a12596ca22d70f7aaa793980f5b036db" +url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_windows_amd64.exe.tar.gz" diff --git a/mise.toml b/mise.toml index 7fa3473d62..65ed2377be 100644 --- a/mise.toml +++ b/mise.toml @@ -2,29 +2,27 @@ experimental_monorepo_root = true [monorepo] config_roots = [ - "plugins", + "packages/plugin-core", "server", - "cli", + "packages/cli", "deployment", "mobile", "e2e", "web", "docs", ".github", + "machine-learning", ] [tools] node = "24.15.0" -flutter = "3.41.7" -pnpm = "10.33.1" -terragrunt = "1.0.2" +pnpm = "10.33.4" +terragrunt = "1.0.3" opentofu = "1.11.6" -java = "21.0.2" - -[tools."github:CQLabs/homebrew-dcm"] -version = "1.37.0" -bin = "dcm" -postinstall = "chmod +x $MISE_TOOL_INSTALL_PATH/dcm" +"npm:oazapfts" = "7.5.0" +"github:extism/cli" = "1.6.3" +"github:webassembly/binaryen" = "version_124" +"github:extism/js-pdk" = "1.6.0" [tools."github:jellyfin/jellyfin-ffmpeg"] version = "7.1.3-6" @@ -38,21 +36,129 @@ macos-arm64 = { asset_pattern = "jellyfin-ffmpeg_*_portable_macarm64-gpl.tar.xz" [settings] experimental = true pin = true +lockfile = true + +[tasks.plugins] +run = [ + "pnpm --filter @immich/sdk --filter @immich/plugin-sdk --filter @immich/plugin-core install --frozen-lockfile", + "pnpm --filter @immich/sdk --filter @immich/plugin-sdk --filter @immich/plugin-core build", +] + +[tasks.open-api-typescript] +run = [ + "oazapfts --optimistic --argumentStyle=object --useEnumType --allSchemas open-api/immich-openapi-specs.json packages/sdk/src/fetch-client.ts", + { task = "//:sdk:install" }, + { task = "//:sdk:build" }, +] + +[tasks.open-api-dart] +dir = "open-api" +run = "bash ./bin/generate-dart-sdk.sh" + +[tasks.open-api] +env = { SHARP_IGNORE_GLOBAL_LIBVIPS = true } +run = [ + { task = "//:plugins" }, + { task = "//server:install" }, + { task = "//server:build" }, + { task = "//server:sync-open-api" }, + { task = ":open-api-typescript" }, + { task = ":open-api-dart" }, +] + +[tasks.sql] +dir = "server" +run = "node ./dist/bin/sync-sql.js" + +# TODO dev, prod, and e2e should be de-duplicated by using env but for some reason I ran into issues +[tasks.dev] +depends = "//:plugins" +dir = "docker" +interactive = true +env = { COMPOSE_BAKE = true } +run = "docker compose -f ./docker-compose.dev.yml up --remove-orphans" +depends_post = "//:dev-down" + +[tasks.dev-update] +run = { task = "//:dev", args = ["--build", "-V"] } + +[tasks.dev-scale] +run = { task = "//:dev", args = ["--build", "-V", "--scale immich-server=3"] } + +[tasks.dev-down] +dir = "docker" +run = "docker compose -f ./docker-compose.dev.yml down --remove-orphans" + +[tasks.prod] +depends = "//:plugins" +dir = "docker" +interactive = true +env = { COMPOSE_BAKE = true } +run = "docker compose -f ./docker-compose.prod.yml up --build --remove-orphans" +depends_post = "//:prod-down" + +[tasks.prod-scale] +run = { task = "//:prod", args = [ + "--build", + "-V", + "--scale immich-server=3", + "--scale immich-microservices", +] } + +[tasks.prod-down] +dir = "docker" +run = "docker compose -f ./docker-compose.prod.yml down --remove-orphans" + +[tasks.e2e] +depends = "//:plugins" +dir = "e2e" +interactive = true +env = { COMPOSE_BAKE = true } +run = "docker compose -f ./docker-compose.yml up --remove-orphans" +depends_post = "//:e2e-down" + +[tasks.e2e-dev] +depends = "//:plugins" +dir = "e2e" +interactive = true +env = { COMPOSE_BAKE = true } +run = "docker compose -f ./docker-compose.dev.yml up --remove-orphans" +depends_post = "//:e2e-dev-down" + +[tasks.e2e-update] +run = { task = "//:e2e", args = ["--build", '-V'] } + +[tasks.e2e-down] +dir = "e2e" +run = "docker compose -f ./docker-compose.yml down --remove-orphans" + +[tasks.e2e-dev-down] +dir = "e2e" +run = "docker compose -f ./docker-compose.dev.yml down --remove-orphans" # SDK tasks [tasks."sdk:install"] -dir = "open-api/typescript-sdk" -run = "pnpm install --filter @immich/sdk --frozen-lockfile" +dir = "packages/sdk" +run = "pnpm --filter @immich/sdk install --frozen-lockfile" [tasks."sdk:build"] -dir = "open-api/typescript-sdk" -run = "pnpm run build" +dir = "packages/sdk" +run = "pnpm build" # i18n tasks [tasks."i18n:format"] -dir = "i18n" -run = "pnpm run format" +run = "pnpm format" [tasks."i18n:format-fix"] -dir = "i18n" -run = "pnpm run format:fix" +run = "pnpm format:fix" + +[tasks.clean] +run = [ + "find . -name 'node_modules' -type d -prune -exec rm -rf '{}' +", + "find . -name 'dist' -type d -prune -exec rm -rf '{}' +", + "find . -name 'build' -type d -prune -exec rm -rf '{}' +", + "find . -name '.svelte-kit' -type d -prune -exec rm -rf '{}' +", + "find . -name 'coverage' -type d -prune -exec rm -rf '{}' +", + "find . -name '.pnpm-store' -type d -prune -exec rm -rf '{}' +", + { task = "//:*-down" }, +] diff --git a/mobile/.vscode/settings.json b/mobile/.vscode/settings.json index 517086e98a..781acecc6d 100644 --- a/mobile/.vscode/settings.json +++ b/mobile/.vscode/settings.json @@ -1,5 +1,4 @@ { - "dart.flutterSdkPath": ".fvm/versions/3.41.9", "dart.lineLength": 120, "[dart]": { "editor.rulers": [ diff --git a/mobile/analysis_options.yaml b/mobile/analysis_options.yaml index fafd1f40ec..7c49052fc2 100644 --- a/mobile/analysis_options.yaml +++ b/mobile/analysis_options.yaml @@ -34,6 +34,7 @@ linter: unrelated_type_equality_checks: true prefer_const_constructors: true always_use_package_imports: true + always_put_control_body_on_new_line: true # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options @@ -50,6 +51,7 @@ analyzer: # - custom_lint errors: unawaited_futures: warning + always_put_control_body_on_new_line: warning custom_lint: rules: diff --git a/mobile/android/app/build.gradle b/mobile/android/app/build.gradle index e879b54ae5..1f22eeaf52 100644 --- a/mobile/android/app/build.gradle +++ b/mobile/android/app/build.gradle @@ -64,8 +64,15 @@ android { } release { - signingConfig signingConfigs.release + def hasKeystore = file("../key.jks").exists() && file("../key.jks").length() > 0 + signingConfig hasKeystore ? signingConfigs.release : signingConfigs.debug proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + + def prNumber = System.getenv("PR_NUMBER") + if (prNumber) { + applicationIdSuffix ".pr${prNumber}" + versionNameSuffix "-pr${prNumber}" + } } } namespace 'app.alextran.immich' @@ -82,6 +89,13 @@ flutter { } dependencies { + constraints { + implementation("androidx.glance:glance-appwidget") { + version { strictly libs.versions.glance.get() } + because 'home_widget requests 1.+ which can resolve to pre-releases incompatible with our compileSdk/AGP' + } + } + implementation libs.okhttp implementation libs.cronet.embedded implementation libs.media3.datasource.okhttp diff --git a/mobile/android/app/src/main/AndroidManifest.xml b/mobile/android/app/src/main/AndroidManifest.xml index 436d8c492d..1b8d2a97fb 100644 --- a/mobile/android/app/src/main/AndroidManifest.xml +++ b/mobile/android/app/src/main/AndroidManifest.xml @@ -89,6 +89,20 @@ + + + + + + + + + + + + + + diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/MainActivity.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/MainActivity.kt index 2c80b8d2bd..fc9ab28fa2 100644 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/MainActivity.kt +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/MainActivity.kt @@ -1,6 +1,7 @@ package app.alextran.immich import android.content.Context +import android.content.Intent import android.os.Build import android.os.ext.SdkExtensions import app.alextran.immich.background.BackgroundEngineLock @@ -17,9 +18,12 @@ import app.alextran.immich.images.LocalImageApi import app.alextran.immich.images.LocalImagesImpl import app.alextran.immich.images.RemoteImageApi import app.alextran.immich.images.RemoteImagesImpl +import app.alextran.immich.permission.PermissionApi +import app.alextran.immich.permission.PermissionApiImpl import app.alextran.immich.sync.NativeSyncApi import app.alextran.immich.sync.NativeSyncApiImpl26 import app.alextran.immich.sync.NativeSyncApiImpl30 +import app.alextran.immich.viewintent.ViewIntentPlugin import io.flutter.embedding.android.FlutterFragmentActivity import io.flutter.embedding.engine.FlutterEngine @@ -29,6 +33,11 @@ class MainActivity : FlutterFragmentActivity() { registerPlugins(this, flutterEngine) } + override fun onNewIntent(intent: Intent) { + super.onNewIntent(intent) + setIntent(intent) + } + companion object { fun registerPlugins(ctx: Context, flutterEngine: FlutterEngine) { HttpClientManager.initialize(ctx) @@ -44,15 +53,19 @@ class MainActivity : FlutterFragmentActivity() { } else { NativeSyncApiImpl30(ctx) } + val permissionApiImpl = PermissionApiImpl(ctx) NativeSyncApi.setUp(messenger, nativeSyncApiImpl) + PermissionApi.setUp(messenger, permissionApiImpl) LocalImageApi.setUp(messenger, LocalImagesImpl(ctx)) RemoteImageApi.setUp(messenger, RemoteImagesImpl(ctx)) BackgroundWorkerFgHostApi.setUp(messenger, BackgroundWorkerApiImpl(ctx)) ConnectivityApi.setUp(messenger, ConnectivityApiImpl(ctx)) + flutterEngine.plugins.add(ViewIntentPlugin()) flutterEngine.plugins.add(backgroundEngineLockImpl) flutterEngine.plugins.add(nativeSyncApiImpl) + flutterEngine.plugins.add(permissionApiImpl) } fun cancelPlugins(flutterEngine: FlutterEngine) { @@ -60,6 +73,8 @@ class MainActivity : FlutterFragmentActivity() { flutterEngine.plugins.get(NativeSyncApiImpl26::class.java) as ImmichPlugin? ?: flutterEngine.plugins.get(NativeSyncApiImpl30::class.java) as ImmichPlugin? nativeApi?.detachFromEngine() + val permissionApi = flutterEngine.plugins.get(PermissionApiImpl::class.java) as ImmichPlugin? + permissionApi?.detachFromEngine() } } } diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/background/BackgroundWorker.g.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/background/BackgroundWorker.g.kt index 0ae49f87f6..3fcaed34bc 100644 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/background/BackgroundWorker.g.kt +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/background/BackgroundWorker.g.kt @@ -416,12 +416,12 @@ class BackgroundWorkerFlutterApi(private val binaryMessenger: BinaryMessenger, p } } } - fun onAndroidUpload(callback: (Result) -> Unit) + fun onAndroidUpload(maxMinutesArg: Long?, callback: (Result) -> Unit) { val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" val channelName = "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFlutterApi.onAndroidUpload$separatedMessageChannelSuffix" val channel = BasicMessageChannel(binaryMessenger, channelName, codec) - channel.send(null) { + channel.send(listOf(maxMinutesArg)) { if (it is List<*>) { if (it.size > 1) { callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/background/BackgroundWorker.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/background/BackgroundWorker.kt index 7dce1f6edf..716477904c 100644 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/background/BackgroundWorker.kt +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/background/BackgroundWorker.kt @@ -107,7 +107,7 @@ class BackgroundWorker(context: Context, params: WorkerParameters) : * This method acts as a bridge between the native Android background task system and Flutter. */ override fun onInitialized() { - flutterApi?.onAndroidUpload { handleHostResult(it) } + flutterApi?.onAndroidUpload(maxMinutesArg = 20) { handleHostResult(it) } } // TODO: Move this to a separate NotificationManager class diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/core/Network.g.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/core/Network.g.kt index 1687a7ba95..c380a0a6a5 100644 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/core/Network.g.kt +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/core/Network.g.kt @@ -315,6 +315,7 @@ interface NetworkApi { fun hasCertificate(): Boolean fun getClientPointer(): Long fun setRequestHeaders(headers: Map, serverUrls: List, token: String?) + fun getAppGroupId(): String companion object { /** The codec used by NetworkApi. */ @@ -430,6 +431,21 @@ interface NetworkApi { channel.setMessageHandler(null) } } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NetworkApi.getAppGroupId$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + val wrapped: List = try { + listOf(api.getAppGroupId()) + } catch (exception: Throwable) { + NetworkPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } } } } diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/core/NetworkApiPlugin.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/core/NetworkApiPlugin.kt index 85b7a6c730..4479ec7701 100644 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/core/NetworkApiPlugin.kt +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/core/NetworkApiPlugin.kt @@ -13,7 +13,7 @@ class NetworkApiPlugin : FlutterPlugin, ActivityAware { private var networkApi: NetworkApiImpl? = null override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) { - networkApi = NetworkApiImpl() + networkApi = NetworkApiImpl(binding.applicationContext) NetworkApi.setUp(binding.binaryMessenger, networkApi) } @@ -39,9 +39,11 @@ class NetworkApiPlugin : FlutterPlugin, ActivityAware { } } -private class NetworkApiImpl : NetworkApi { +private class NetworkApiImpl(private val context: Context) : NetworkApi { var activity: Activity? = null + override fun getAppGroupId(): String = context.packageName + override fun addCertificate(clientData: ClientCertData, callback: (Result) -> Unit) { try { HttpClientManager.setKeyEntry(clientData.data, clientData.password.toCharArray()) diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/images/RemoteImagesImpl.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/images/RemoteImagesImpl.kt index 9255eff44b..f7ebc349f6 100644 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/images/RemoteImagesImpl.kt +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/images/RemoteImagesImpl.kt @@ -23,6 +23,8 @@ import java.io.IOException import java.nio.ByteBuffer import java.util.concurrent.ConcurrentHashMap +private const val MAX_PREALLOC_BYTES = 128 * 1024 * 1024 + private class RemoteRequest(val cancellationSignal: CancellationSignal) class RemoteImagesImpl(context: Context) : RemoteImageApi { @@ -228,7 +230,6 @@ private class CronetImageFetcher : ImageFetcher { private val onComplete: () -> Unit, ) : UrlRequest.Callback() { private var buffer: NativeByteBuffer? = null - private var wrapped: ByteBuffer? = null private var error: Exception? = null override fun onRedirectReceived(request: UrlRequest, info: UrlResponseInfo, newUrl: String) { @@ -242,15 +243,16 @@ private class CronetImageFetcher : ImageFetcher { } try { + // Content-Length is a size hint only. With Content-Encoding (gzip/br/...), + // Cronet auto-decompresses and writes decompressed bytes to our buffer, which + // may exceed the wire/compressed Content-Length. Always use the growable + // buffer path so we can't overflow. val contentLength = info.allHeaders["content-length"]?.firstOrNull()?.toIntOrNull() ?: 0 - if (contentLength > 0) { - buffer = NativeByteBuffer(contentLength + 1) - wrapped = NativeBuffer.wrap(buffer!!.pointer, contentLength + 1) - request.read(wrapped) - } else { - buffer = NativeByteBuffer(INITIAL_BUFFER_SIZE) - request.read(buffer!!.wrapRemaining()) - } + // Cap the up-front alloc: Content-Length is untrusted and can be huge or near + // Int.MAX_VALUE (overflowing `+1`). For larger responses the grow path takes over. + val initialSize = if (contentLength in 1..MAX_PREALLOC_BYTES) contentLength + 1 else INITIAL_BUFFER_SIZE + buffer = NativeByteBuffer(initialSize) + request.read(buffer!!.wrapRemaining()) } catch (e: Exception) { error = e return request.cancel() @@ -263,14 +265,14 @@ private class CronetImageFetcher : ImageFetcher { byteBuffer: ByteBuffer ) { try { - val buf = if (wrapped == null) { - buffer!!.run { - advance(byteBuffer.position()) - ensureHeadroom() - wrapRemaining() - } - } else { - wrapped + // Always pass a fresh wrap so byteBuffer.position() represents only the + // bytes Cronet wrote in this iteration. Reusing the caller-supplied + // ByteBuffer breaks advance(): Cronet's position keeps accumulating + // across reads, which would double-count previous iterations' bytes. + val buf = buffer!!.run { + advance(byteBuffer.position()) + ensureHeadroom() + wrapRemaining() } request.read(buf) } catch (e: Exception) { @@ -280,7 +282,6 @@ private class CronetImageFetcher : ImageFetcher { } override fun onSucceeded(request: UrlRequest, info: UrlResponseInfo) { - wrapped?.let { buffer!!.advance(it.position()) } onSuccess(buffer!!) onComplete() } diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/permission/ManageMediaPermissionDelegate.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/permission/ManageMediaPermissionDelegate.kt new file mode 100644 index 0000000000..ddabfbabd8 --- /dev/null +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/permission/ManageMediaPermissionDelegate.kt @@ -0,0 +1,96 @@ +package app.alextran.immich.permission + +import android.content.Context +import android.content.Intent +import android.os.Build +import android.provider.MediaStore +import android.provider.Settings +import androidx.core.net.toUri +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding +import io.flutter.plugin.common.PluginRegistry + +class ManageMediaPermissionDelegate( + context: Context, + private val requestCode: Int = 1003, +) : PluginRegistry.ActivityResultListener { + private val ctx = context.applicationContext + private var activityBinding: ActivityPluginBinding? = null + private var pendingResult: ((Result) -> Unit)? = null + + fun hasManageMediaPermission(): Boolean { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + MediaStore.canManageMedia(ctx) + } else { + false + } + } + + fun requestManageMediaPermission(callback: (Result) -> Unit) { + if (hasManageMediaPermission()) { + callback(Result.success(true)) + return + } + + openManageMediaPermissionSettings(callback) + } + + fun manageMediaPermission(callback: (Result) -> Unit) { + openManageMediaPermissionSettings(callback) + } + + private fun openManageMediaPermissionSettings(callback: (Result) -> Unit) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { + callback(Result.success(false)) + return + } + + val activity = activityBinding?.activity + if (activity == null) { + callback(Result.failure(FlutterError("NO_ACTIVITY", "Activity not available", null))) + return + } + + pendingResult = callback + val intent = Intent(Settings.ACTION_REQUEST_MANAGE_MEDIA).apply { + data = "package:${activity.packageName}".toUri() + } + try { + activity.startActivityForResult(intent, requestCode) + } catch (e: Exception) { + pendingResult = null + callback( + Result.failure( + FlutterError("ACTIVITY_LAUNCH_FAILED", "Failed to launch MANAGE_MEDIA settings", e.toString()) + ) + ) + } + } + + fun onAttachedToActivity(binding: ActivityPluginBinding) { + activityBinding = binding + binding.addActivityResultListener(this) + } + + fun onDetachedFromActivity() { + failPending("ACTIVITY_DETACHED", "Activity detached before MANAGE_MEDIA result") + activityBinding?.removeActivityResultListener(this) + activityBinding = null + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean { + if (requestCode == this.requestCode) { + val callback = pendingResult + pendingResult = null + callback?.invoke(Result.success(hasManageMediaPermission())) + return true + } + + return false + } + + private fun failPending(code: String, message: String) { + val callback = pendingResult ?: return + pendingResult = null + callback(Result.failure(FlutterError(code, message, null))) + } +} diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/permission/PermissionApi.g.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/permission/PermissionApi.g.kt new file mode 100644 index 0000000000..48a1a72037 --- /dev/null +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/permission/PermissionApi.g.kt @@ -0,0 +1,128 @@ +// Autogenerated from Pigeon (v26.3.4), do not edit directly. +// See also: https://pub.dev/packages/pigeon +@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass") + +package app.alextran.immich.permission + +import android.util.Log +import io.flutter.plugin.common.BasicMessageChannel +import io.flutter.plugin.common.BinaryMessenger +import io.flutter.plugin.common.EventChannel +import io.flutter.plugin.common.MessageCodec +import io.flutter.plugin.common.StandardMethodCodec +import io.flutter.plugin.common.StandardMessageCodec +import java.io.ByteArrayOutputStream +import java.nio.ByteBuffer +private object PermissionApiPigeonUtils { + + fun wrapResult(result: Any?): List { + return listOf(result) + } + + fun wrapError(exception: Throwable): List { + return if (exception is FlutterError) { + listOf( + exception.code, + exception.message, + exception.details + ) + } else { + listOf( + exception.javaClass.simpleName, + exception.toString(), + "Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception) + ) + } + } +} + +/** + * Error class for passing custom error details to Flutter via a thrown PlatformException. + * @property code The error code. + * @property message The error message. + * @property details The error details. Must be a datatype supported by the api codec. + */ +class FlutterError ( + val code: String, + override val message: String? = null, + val details: Any? = null +) : RuntimeException() +private open class PermissionApiPigeonCodec : StandardMessageCodec() { + override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? { + return super.readValueOfType(type, buffer) + } + override fun writeValue(stream: ByteArrayOutputStream, value: Any?) { + super.writeValue(stream, value) + } +} + + +/** Generated interface from Pigeon that represents a handler of messages from Flutter. */ +interface PermissionApi { + fun hasManageMediaPermission(): Boolean + fun requestManageMediaPermission(callback: (Result) -> Unit) + fun manageMediaPermission(callback: (Result) -> Unit) + + companion object { + /** The codec used by PermissionApi. */ + val codec: MessageCodec by lazy { + PermissionApiPigeonCodec() + } + /** Sets up an instance of `PermissionApi` to handle messages through the `binaryMessenger`. */ + @JvmOverloads + fun setUp(binaryMessenger: BinaryMessenger, api: PermissionApi?, messageChannelSuffix: String = "") { + val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.PermissionApi.hasManageMediaPermission$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + val wrapped: List = try { + listOf(api.hasManageMediaPermission()) + } catch (exception: Throwable) { + PermissionApiPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.PermissionApi.requestManageMediaPermission$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + api.requestManageMediaPermission{ result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(PermissionApiPigeonUtils.wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(PermissionApiPigeonUtils.wrapResult(data)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.PermissionApi.manageMediaPermission$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + api.manageMediaPermission{ result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(PermissionApiPigeonUtils.wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(PermissionApiPigeonUtils.wrapResult(data)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + } + } +} diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/permission/PermissionApiImpl.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/permission/PermissionApiImpl.kt new file mode 100644 index 0000000000..c3443bb06d --- /dev/null +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/permission/PermissionApiImpl.kt @@ -0,0 +1,37 @@ +package app.alextran.immich.permission + +import android.content.Context +import app.alextran.immich.core.ImmichPlugin +import io.flutter.embedding.engine.plugins.activity.ActivityAware +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding + +class PermissionApiImpl(context: Context) : ImmichPlugin(), PermissionApi, ActivityAware { + private val manageMediaPermissionDelegate = ManageMediaPermissionDelegate(context) + + override fun hasManageMediaPermission(): Boolean = + manageMediaPermissionDelegate.hasManageMediaPermission() + + override fun requestManageMediaPermission(callback: (Result) -> Unit) { + manageMediaPermissionDelegate.requestManageMediaPermission { completeWhenActive(callback, it) } + } + + override fun manageMediaPermission(callback: (Result) -> Unit) { + manageMediaPermissionDelegate.manageMediaPermission { completeWhenActive(callback, it) } + } + + override fun onAttachedToActivity(binding: ActivityPluginBinding) { + manageMediaPermissionDelegate.onAttachedToActivity(binding) + } + + override fun onDetachedFromActivityForConfigChanges() { + manageMediaPermissionDelegate.onDetachedFromActivity() + } + + override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { + manageMediaPermissionDelegate.onAttachedToActivity(binding) + } + + override fun onDetachedFromActivity() { + manageMediaPermissionDelegate.onDetachedFromActivity() + } +} diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MediaTrashDelegate.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MediaTrashDelegate.kt new file mode 100644 index 0000000000..fbf4d7e919 --- /dev/null +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MediaTrashDelegate.kt @@ -0,0 +1,133 @@ +package app.alextran.immich.sync + +import android.app.Activity +import android.content.ContentResolver +import android.content.ContentUris +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.Bundle +import android.provider.MediaStore +import androidx.annotation.RequiresApi +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding +import io.flutter.plugin.common.PluginRegistry + +class MediaTrashDelegate( + context: Context, + private val trashRequestCode: Int = 1002, +) : PluginRegistry.ActivityResultListener { + private val ctx = context.applicationContext + private var activityBinding: ActivityPluginBinding? = null + private var pendingResult: ((Result) -> Unit)? = null + + private fun hasManageMediaPermission(): Boolean { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + MediaStore.canManageMedia(ctx) + } else { + false + } + } + + fun restoreFromTrashById(mediaId: String, type: Long, callback: (Result) -> Unit) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R || !hasManageMediaPermission()) { + callback(Result.failure(FlutterError("PERMISSION_DENIED", "Media permission required", null))) + return + } + + val id = mediaId.toLongOrNull() + if (id == null) { + callback(Result.failure(FlutterError("INVALID_ID", "The file id is not a valid number: $mediaId", null))) + return + } + + if (!isInTrash(id)) { + callback(Result.failure(FlutterError("TRASH_NOT_FOUND", "Item with id=$id not found in trash", null))) + return + } + + restoreUri(ContentUris.withAppendedId(contentUriForType(type.toInt()), id), callback) + } + + @RequiresApi(Build.VERSION_CODES.R) + private fun restoreUri( + contentUri: Uri, + callback: (Result) -> Unit, + ) { + val activity = activityBinding?.activity + if (activity == null) { + callback(Result.failure(FlutterError("NO_ACTIVITY", "Activity not available", null))) + return + } + + try { + val pendingIntent = MediaStore.createTrashRequest(ctx.contentResolver, listOf(contentUri), false) + pendingResult = callback + activity.startIntentSenderForResult( + pendingIntent.intentSender, + trashRequestCode, + null, + 0, + 0, + 0, + ) + } catch (e: Exception) { + pendingResult = null + callback( + Result.failure( + FlutterError("TRASH_ERROR", "Error creating or starting trash request", e.toString()) + ) + ) + } + } + + @RequiresApi(Build.VERSION_CODES.R) + private fun isInTrash(id: Long): Boolean { + val filesUri = MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL) + val args = Bundle().apply { + putString(ContentResolver.QUERY_ARG_SQL_SELECTION, "${MediaStore.Files.FileColumns._ID}=?") + putStringArray(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS, arrayOf(id.toString())) + putInt(MediaStore.QUERY_ARG_MATCH_TRASHED, MediaStore.MATCH_ONLY) + putInt(ContentResolver.QUERY_ARG_LIMIT, 1) + } + return ctx.contentResolver.query(filesUri, arrayOf(MediaStore.Files.FileColumns._ID), args, null) + ?.use { it.moveToFirst() } == true + } + + private fun contentUriForType(type: Int): Uri = + when (type) { + // Same order as AssetType from Dart. + 1 -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI + 2 -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI + 3 -> MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + else -> MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL) + } + + fun onAttachedToActivity(binding: ActivityPluginBinding) { + activityBinding = binding + binding.addActivityResultListener(this) + } + + fun onDetachedFromActivity() { + failPending("ACTIVITY_DETACHED", "Activity detached before trash result") + activityBinding?.removeActivityResultListener(this) + activityBinding = null + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean { + if (requestCode == trashRequestCode) { + val callback = pendingResult + pendingResult = null + callback?.invoke(Result.success(resultCode == Activity.RESULT_OK)) + return true + } + + return false + } + + private fun failPending(code: String, message: String) { + val callback = pendingResult ?: return + pendingResult = null + callback(Result.failure(FlutterError(code, message, null))) + } +} diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/Messages.g.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/Messages.g.kt index b49664dea5..02f1cb237d 100644 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/Messages.g.kt +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/Messages.g.kt @@ -542,17 +542,19 @@ private open class MessagesPigeonCodec : StandardMessageCodec() { /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ interface NativeSyncApi { - fun shouldFullSync(): Boolean - fun getMediaChanges(): SyncDelta + fun shouldFullSync(callback: (Result) -> Unit) + fun getMediaChanges(callback: (Result) -> Unit) fun checkpointSync() fun clearSyncCheckpoint() - fun getAssetIdsForAlbum(albumId: String): List - fun getAlbums(): List + fun getAssetIdsForAlbum(albumId: String, callback: (Result>) -> Unit) + fun getAlbums(callback: (Result>) -> Unit) fun getAssetsCountSince(albumId: String, timestamp: Long): Long - fun getAssetsForAlbum(albumId: String, updatedTimeCond: Long?): List + fun getAssetsForAlbum(albumId: String, updatedTimeCond: Long?, callback: (Result>) -> Unit) fun hashAssets(assetIds: List, allowNetworkAccess: Boolean, callback: (Result>) -> Unit) fun cancelHashing() + fun cancelSync() fun getTrashedAssets(): Map> + fun restoreFromTrashById(mediaId: String, type: Long, callback: (Result) -> Unit) fun getCloudIdForAssetIds(assetIds: List): List companion object { @@ -569,27 +571,33 @@ interface NativeSyncApi { val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.shouldFullSync$separatedMessageChannelSuffix", codec) if (api != null) { channel.setMessageHandler { _, reply -> - val wrapped: List = try { - listOf(api.shouldFullSync()) - } catch (exception: Throwable) { - MessagesPigeonUtils.wrapError(exception) + api.shouldFullSync{ result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(MessagesPigeonUtils.wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(MessagesPigeonUtils.wrapResult(data)) + } } - reply.reply(wrapped) } } else { channel.setMessageHandler(null) } } run { - val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getMediaChanges$separatedMessageChannelSuffix", codec, taskQueue) + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getMediaChanges$separatedMessageChannelSuffix", codec) if (api != null) { channel.setMessageHandler { _, reply -> - val wrapped: List = try { - listOf(api.getMediaChanges()) - } catch (exception: Throwable) { - MessagesPigeonUtils.wrapError(exception) + api.getMediaChanges{ result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(MessagesPigeonUtils.wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(MessagesPigeonUtils.wrapResult(data)) + } } - reply.reply(wrapped) } } else { channel.setMessageHandler(null) @@ -628,32 +636,38 @@ interface NativeSyncApi { } } run { - val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAssetIdsForAlbum$separatedMessageChannelSuffix", codec, taskQueue) + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAssetIdsForAlbum$separatedMessageChannelSuffix", codec) if (api != null) { channel.setMessageHandler { message, reply -> val args = message as List val albumIdArg = args[0] as String - val wrapped: List = try { - listOf(api.getAssetIdsForAlbum(albumIdArg)) - } catch (exception: Throwable) { - MessagesPigeonUtils.wrapError(exception) + api.getAssetIdsForAlbum(albumIdArg) { result: Result> -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(MessagesPigeonUtils.wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(MessagesPigeonUtils.wrapResult(data)) + } } - reply.reply(wrapped) } } else { channel.setMessageHandler(null) } } run { - val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAlbums$separatedMessageChannelSuffix", codec, taskQueue) + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAlbums$separatedMessageChannelSuffix", codec) if (api != null) { channel.setMessageHandler { _, reply -> - val wrapped: List = try { - listOf(api.getAlbums()) - } catch (exception: Throwable) { - MessagesPigeonUtils.wrapError(exception) + api.getAlbums{ result: Result> -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(MessagesPigeonUtils.wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(MessagesPigeonUtils.wrapResult(data)) + } } - reply.reply(wrapped) } } else { channel.setMessageHandler(null) @@ -678,18 +692,21 @@ interface NativeSyncApi { } } run { - val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAssetsForAlbum$separatedMessageChannelSuffix", codec, taskQueue) + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAssetsForAlbum$separatedMessageChannelSuffix", codec) if (api != null) { channel.setMessageHandler { message, reply -> val args = message as List val albumIdArg = args[0] as String val updatedTimeCondArg = args[1] as Long? - val wrapped: List = try { - listOf(api.getAssetsForAlbum(albumIdArg, updatedTimeCondArg)) - } catch (exception: Throwable) { - MessagesPigeonUtils.wrapError(exception) + api.getAssetsForAlbum(albumIdArg, updatedTimeCondArg) { result: Result> -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(MessagesPigeonUtils.wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(MessagesPigeonUtils.wrapResult(data)) + } } - reply.reply(wrapped) } } else { channel.setMessageHandler(null) @@ -732,6 +749,22 @@ interface NativeSyncApi { channel.setMessageHandler(null) } } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.cancelSync$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + val wrapped: List = try { + api.cancelSync() + listOf(null) + } catch (exception: Throwable) { + MessagesPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } run { val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getTrashedAssets$separatedMessageChannelSuffix", codec, taskQueue) if (api != null) { @@ -747,6 +780,27 @@ interface NativeSyncApi { channel.setMessageHandler(null) } } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.restoreFromTrashById$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val mediaIdArg = args[0] as String + val typeArg = args[1] as Long + api.restoreFromTrashById(mediaIdArg, typeArg) { result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(MessagesPigeonUtils.wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(MessagesPigeonUtils.wrapResult(data)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } run { val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getCloudIdForAssetIds$separatedMessageChannelSuffix", codec, taskQueue) if (api != null) { diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImpl26.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImpl26.kt index 6d2c35d78f..180e23286c 100644 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImpl26.kt +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImpl26.kt @@ -4,7 +4,11 @@ import android.content.Context class NativeSyncApiImpl26(context: Context) : NativeSyncApiImplBase(context), NativeSyncApi { - override fun shouldFullSync(): Boolean { + override fun shouldFullSync(callback: (Result) -> Unit) { + runSync(callback) { shouldFullSync() } + } + + private fun shouldFullSync(): Boolean { return true } @@ -18,7 +22,11 @@ class NativeSyncApiImpl26(context: Context) : NativeSyncApiImplBase(context), Na // No-op for Android 10 and below } - override fun getMediaChanges(): SyncDelta { + override fun getMediaChanges(callback: (Result) -> Unit) { + runSync(callback) { getMediaChanges() } + } + + private fun getMediaChanges(): SyncDelta { throw IllegalStateException("Method not supported on this Android version.") } diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImpl30.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImpl30.kt index ca54c9f823..4785b751c0 100644 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImpl30.kt +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImpl30.kt @@ -7,6 +7,8 @@ import android.os.Bundle import android.provider.MediaStore import androidx.annotation.RequiresApi import androidx.annotation.RequiresExtension +import kotlinx.coroutines.currentCoroutineContext +import kotlinx.coroutines.ensureActive import kotlinx.serialization.json.Json @RequiresApi(Build.VERSION_CODES.Q) @@ -35,7 +37,11 @@ class NativeSyncApiImpl30(context: Context) : NativeSyncApiImplBase(context), Na } } - override fun shouldFullSync(): Boolean = + override fun shouldFullSync(callback: (Result) -> Unit) { + runSync(callback) { shouldFullSync() } + } + + private fun shouldFullSync(): Boolean = MediaStore.getVersion(ctx) != prefs.getString(SHARED_PREF_MEDIA_STORE_VERSION_KEY, null) override fun checkpointSync() { @@ -49,7 +55,11 @@ class NativeSyncApiImpl30(context: Context) : NativeSyncApiImplBase(context), Na } } - override fun getMediaChanges(): SyncDelta { + override fun getMediaChanges(callback: (Result) -> Unit) { + runSync(callback) { getMediaChanges() } + } + + private suspend fun getMediaChanges(): SyncDelta { val genMap = getSavedGenerationMap() val currentVolumes = MediaStore.getExternalVolumeNames(ctx) val changed = mutableListOf() @@ -58,6 +68,7 @@ class NativeSyncApiImpl30(context: Context) : NativeSyncApiImplBase(context), Na var hasChanges = genMap.keys != currentVolumes for (volume in currentVolumes) { + currentCoroutineContext().ensureActive() val currentGen = MediaStore.getGeneration(ctx, volume) val storedGen = genMap[volume] ?: 0 if (currentGen <= storedGen) { diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImplBase.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImplBase.kt index 777a565fe3..18b771a613 100644 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImplBase.kt +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImplBase.kt @@ -17,6 +17,8 @@ import com.bumptech.glide.Glide import com.bumptech.glide.load.ImageHeaderParser import com.bumptech.glide.load.ImageHeaderParserUtils import com.bumptech.glide.load.resource.bitmap.DefaultImageHeaderParser +import io.flutter.embedding.engine.plugins.activity.ActivityAware +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -39,15 +41,18 @@ sealed class AssetResult { private const val TAG = "NativeSyncApiImplBase" @SuppressLint("InlinedApi") -open class NativeSyncApiImplBase(context: Context) : ImmichPlugin() { +open class NativeSyncApiImplBase(context: Context) : ImmichPlugin(), ActivityAware { private val ctx: Context = context.applicationContext private var hashTask: Job? = null + private var syncJob: Job? = null + private val mediaTrashDelegate = MediaTrashDelegate(ctx) companion object { private const val MAX_CONCURRENT_HASH_OPERATIONS = 16 private val hashSemaphore = Semaphore(MAX_CONCURRENT_HASH_OPERATIONS) private const val HASHING_CANCELLED_CODE = "HASH_CANCELLED" + private const val SYNC_CANCELLED_CODE = "SYNC_CANCELLED" // MediaStore.Files.FileColumns.SPECIAL_FORMAT โ€” S Extensions 21+ // https://developer.android.com/reference/android/provider/MediaStore.Files.FileColumns#SPECIAL_FORMAT @@ -292,7 +297,11 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin() { return PlatformAssetPlaybackStyle.IMAGE } - fun getAlbums(): List { + fun getAlbums(callback: (Result>) -> Unit) { + runSync(callback) { getAlbums() } + } + + private suspend fun getAlbums(): List { val albums = mutableListOf() val albumsCount = mutableMapOf() @@ -319,6 +328,7 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin() { cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DATE_MODIFIED) while (cursor.moveToNext()) { + currentCoroutineContext().ensureActive() val id = cursor.getString(bucketIdColumn) val count = albumsCount.getOrDefault(id, 0) @@ -339,7 +349,11 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin() { .sortedBy { it.id } } - fun getAssetIdsForAlbum(albumId: String): List { + fun getAssetIdsForAlbum(albumId: String, callback: (Result>) -> Unit) { + runSync(callback) { getAssetIdsForAlbum(albumId) } + } + + private fun getAssetIdsForAlbum(albumId: String): List { val projection = arrayOf(MediaStore.MediaColumns._ID) return getCursor( @@ -363,7 +377,11 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin() { )?.use { cursor -> cursor.count.toLong() } ?: 0L - fun getAssetsForAlbum(albumId: String, updatedTimeCond: Long?): List { + fun getAssetsForAlbum(albumId: String, updatedTimeCond: Long?, callback: (Result>) -> Unit) { + runSync(callback) { getAssetsForAlbum(albumId, updatedTimeCond) } + } + + private fun getAssetsForAlbum(albumId: String, updatedTimeCond: Long?): List { var selection = "$BUCKET_SELECTION AND $MEDIA_SELECTION" val selectionArgs = mutableListOf(albumId, *MEDIA_SELECTION_ARGS) @@ -448,6 +466,44 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin() { hashTask = null } + fun cancelSync() { + syncJob?.cancel() + syncJob = null + } + + protected fun runSync(callback: (Result) -> Unit, work: suspend () -> T) { + syncJob?.cancel() + syncJob = CoroutineScope(Dispatchers.IO).launch { + try { + completeWhenActive(callback, Result.success(work())) + } catch (e: CancellationException) { + completeWhenActive(callback, Result.failure(FlutterError(SYNC_CANCELLED_CODE, "Sync cancelled", null))) + } catch (e: Exception) { + completeWhenActive(callback, Result.failure(e)) + } + } + } + + fun restoreFromTrashById(mediaId: String, type: Long, callback: (Result) -> Unit) { + mediaTrashDelegate.restoreFromTrashById(mediaId, type) { completeWhenActive(callback, it) } + } + + override fun onAttachedToActivity(binding: ActivityPluginBinding) { + mediaTrashDelegate.onAttachedToActivity(binding) + } + + override fun onDetachedFromActivityForConfigChanges() { + mediaTrashDelegate.onDetachedFromActivity() + } + + override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { + mediaTrashDelegate.onAttachedToActivity(binding) + } + + override fun onDetachedFromActivity() { + mediaTrashDelegate.onDetachedFromActivity() + } + // This method is only implemented on iOS; on Android, we do not have a concept of cloud IDs @Suppress("unused", "UNUSED_PARAMETER") fun getCloudIdForAssetIds(assetIds: List): List { diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/viewintent/ViewIntent.g.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/viewintent/ViewIntent.g.kt new file mode 100644 index 0000000000..1d5af15cb4 --- /dev/null +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/viewintent/ViewIntent.g.kt @@ -0,0 +1,292 @@ +// Autogenerated from Pigeon (v26.3.4), do not edit directly. +// See also: https://pub.dev/packages/pigeon +@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass") + +package app.alextran.immich.viewintent + +import android.util.Log +import io.flutter.plugin.common.BasicMessageChannel +import io.flutter.plugin.common.BinaryMessenger +import io.flutter.plugin.common.EventChannel +import io.flutter.plugin.common.MessageCodec +import io.flutter.plugin.common.StandardMethodCodec +import io.flutter.plugin.common.StandardMessageCodec +import java.io.ByteArrayOutputStream +import java.nio.ByteBuffer +private object ViewIntentPigeonUtils { + + fun wrapResult(result: Any?): List { + return listOf(result) + } + + fun wrapError(exception: Throwable): List { + return if (exception is FlutterError) { + listOf( + exception.code, + exception.message, + exception.details + ) + } else { + listOf( + exception.javaClass.simpleName, + exception.toString(), + "Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception) + ) + } + } + fun doubleEquals(a: Double, b: Double): Boolean { + // Normalize -0.0 to 0.0 and handle NaN equality. + return (if (a == 0.0) 0.0 else a) == (if (b == 0.0) 0.0 else b) || (a.isNaN() && b.isNaN()) + } + + fun floatEquals(a: Float, b: Float): Boolean { + // Normalize -0.0 to 0.0 and handle NaN equality. + return (if (a == 0.0f) 0.0f else a) == (if (b == 0.0f) 0.0f else b) || (a.isNaN() && b.isNaN()) + } + + fun doubleHash(d: Double): Int { + // Normalize -0.0 to 0.0 and handle NaN to ensure consistent hash codes. + val normalized = if (d == 0.0) 0.0 else d + val bits = java.lang.Double.doubleToLongBits(normalized) + return (bits xor (bits ushr 32)).toInt() + } + + fun floatHash(f: Float): Int { + // Normalize -0.0 to 0.0 and handle NaN to ensure consistent hash codes. + val normalized = if (f == 0.0f) 0.0f else f + return java.lang.Float.floatToIntBits(normalized) + } + + fun deepEquals(a: Any?, b: Any?): Boolean { + if (a === b) { + return true + } + if (a == null || b == null) { + return false + } + if (a is ByteArray && b is ByteArray) { + return a.contentEquals(b) + } + if (a is IntArray && b is IntArray) { + return a.contentEquals(b) + } + if (a is LongArray && b is LongArray) { + return a.contentEquals(b) + } + if (a is DoubleArray && b is DoubleArray) { + if (a.size != b.size) return false + for (i in a.indices) { + if (!doubleEquals(a[i], b[i])) return false + } + return true + } + if (a is FloatArray && b is FloatArray) { + if (a.size != b.size) return false + for (i in a.indices) { + if (!floatEquals(a[i], b[i])) return false + } + return true + } + if (a is Array<*> && b is Array<*>) { + if (a.size != b.size) return false + for (i in a.indices) { + if (!deepEquals(a[i], b[i])) return false + } + return true + } + if (a is List<*> && b is List<*>) { + if (a.size != b.size) return false + val iterA = a.iterator() + val iterB = b.iterator() + while (iterA.hasNext() && iterB.hasNext()) { + if (!deepEquals(iterA.next(), iterB.next())) return false + } + return true + } + if (a is Map<*, *> && b is Map<*, *>) { + if (a.size != b.size) return false + for (entry in a) { + val key = entry.key + var found = false + for (bEntry in b) { + if (deepEquals(key, bEntry.key)) { + if (deepEquals(entry.value, bEntry.value)) { + found = true + break + } else { + return false + } + } + } + if (!found) return false + } + return true + } + if (a is Double && b is Double) { + return doubleEquals(a, b) + } + if (a is Float && b is Float) { + return floatEquals(a, b) + } + return a == b + } + + fun deepHash(value: Any?): Int { + return when (value) { + null -> 0 + is ByteArray -> value.contentHashCode() + is IntArray -> value.contentHashCode() + is LongArray -> value.contentHashCode() + is DoubleArray -> { + var result = 1 + for (item in value) { + result = 31 * result + doubleHash(item) + } + result + } + is FloatArray -> { + var result = 1 + for (item in value) { + result = 31 * result + floatHash(item) + } + result + } + is Array<*> -> { + var result = 1 + for (item in value) { + result = 31 * result + deepHash(item) + } + result + } + is List<*> -> { + var result = 1 + for (item in value) { + result = 31 * result + deepHash(item) + } + result + } + is Map<*, *> -> { + var result = 0 + for (entry in value) { + result += ((deepHash(entry.key) * 31) xor deepHash(entry.value)) + } + result + } + is Double -> doubleHash(value) + is Float -> floatHash(value) + else -> value.hashCode() + } + } + +} + +/** + * Error class for passing custom error details to Flutter via a thrown PlatformException. + * @property code The error code. + * @property message The error message. + * @property details The error details. Must be a datatype supported by the api codec. + */ +class FlutterError ( + val code: String, + override val message: String? = null, + val details: Any? = null +) : RuntimeException() + +/** Generated class from Pigeon that represents data sent in messages. */ +data class ViewIntentPayload ( + val path: String? = null, + val mimeType: String, + val localAssetId: String? = null +) + { + companion object { + fun fromList(pigeonVar_list: List): ViewIntentPayload { + val path = pigeonVar_list[0] as String? + val mimeType = pigeonVar_list[1] as String + val localAssetId = pigeonVar_list[2] as String? + return ViewIntentPayload(path, mimeType, localAssetId) + } + } + fun toList(): List { + return listOf( + path, + mimeType, + localAssetId, + ) + } + override fun equals(other: Any?): Boolean { + if (other == null || other.javaClass != javaClass) { + return false + } + if (this === other) { + return true + } + val other = other as ViewIntentPayload + return ViewIntentPigeonUtils.deepEquals(this.path, other.path) && ViewIntentPigeonUtils.deepEquals(this.mimeType, other.mimeType) && ViewIntentPigeonUtils.deepEquals(this.localAssetId, other.localAssetId) + } + + override fun hashCode(): Int { + var result = javaClass.hashCode() + result = 31 * result + ViewIntentPigeonUtils.deepHash(this.path) + result = 31 * result + ViewIntentPigeonUtils.deepHash(this.mimeType) + result = 31 * result + ViewIntentPigeonUtils.deepHash(this.localAssetId) + return result + } +} +private open class ViewIntentPigeonCodec : StandardMessageCodec() { + override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? { + return when (type) { + 129.toByte() -> { + return (readValue(buffer) as? List)?.let { + ViewIntentPayload.fromList(it) + } + } + else -> super.readValueOfType(type, buffer) + } + } + override fun writeValue(stream: ByteArrayOutputStream, value: Any?) { + when (value) { + is ViewIntentPayload -> { + stream.write(129) + writeValue(stream, value.toList()) + } + else -> super.writeValue(stream, value) + } + } +} + + +/** Generated interface from Pigeon that represents a handler of messages from Flutter. */ +interface ViewIntentHostApi { + fun consumeViewIntent(callback: (Result) -> Unit) + + companion object { + /** The codec used by ViewIntentHostApi. */ + val codec: MessageCodec by lazy { + ViewIntentPigeonCodec() + } + /** Sets up an instance of `ViewIntentHostApi` to handle messages through the `binaryMessenger`. */ + @JvmOverloads + fun setUp(binaryMessenger: BinaryMessenger, api: ViewIntentHostApi?, messageChannelSuffix: String = "") { + val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.ViewIntentHostApi.consumeViewIntent$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + api.consumeViewIntent{ result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(ViewIntentPigeonUtils.wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(ViewIntentPigeonUtils.wrapResult(data)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + } + } +} diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/viewintent/ViewIntentPlugin.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/viewintent/ViewIntentPlugin.kt new file mode 100644 index 0000000000..a1e1fea3dd --- /dev/null +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/viewintent/ViewIntentPlugin.kt @@ -0,0 +1,201 @@ +package app.alextran.immich.viewintent + +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.provider.DocumentsContract +import android.provider.MediaStore +import android.provider.OpenableColumns +import android.util.Log +import android.webkit.MimeTypeMap +import io.flutter.embedding.engine.plugins.FlutterPlugin +import io.flutter.embedding.engine.plugins.activity.ActivityAware +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding +import io.flutter.plugin.common.PluginRegistry +import java.io.File +import java.io.FileOutputStream +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel +import kotlinx.coroutines.launch + +private const val TAG = "ViewIntentPlugin" + +class ViewIntentPlugin : FlutterPlugin, ActivityAware, PluginRegistry.NewIntentListener, ViewIntentHostApi { + private var context: Context? = null + private var activity: Activity? = null + private var unconsumedIntent: Intent? = null + private val ioScope = CoroutineScope(SupervisorJob() + Dispatchers.IO) + + override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) { + context = binding.applicationContext + ViewIntentHostApi.setUp(binding.binaryMessenger, this) + } + + override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { + ViewIntentHostApi.setUp(binding.binaryMessenger, null) + ioScope.cancel() + context = null + } + + override fun onAttachedToActivity(binding: ActivityPluginBinding) { + activity = binding.activity + unconsumedIntent = binding.activity.intent + binding.addOnNewIntentListener(this) + } + + override fun onDetachedFromActivityForConfigChanges() { + activity = null + } + + override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { + onAttachedToActivity(binding) + } + + override fun onDetachedFromActivity() { + activity = null + } + + override fun onNewIntent(intent: Intent): Boolean { + unconsumedIntent = intent + return false + } + + override fun consumeViewIntent(callback: (Result) -> Unit) { + val context = context ?: run { + callback(Result.success(null)) + return + } + val intent = unconsumedIntent ?: activity?.intent + + if (intent?.action != Intent.ACTION_VIEW) { + callback(Result.success(null)) + return + } + + val uri = intent.data + if (uri == null) { + callback(Result.success(null)) + return + } + + ioScope.launch { + try { + val mimeType = context.contentResolver.getType(uri) ?: intent.type + if (mimeType == null || (!mimeType.startsWith("image/") && !mimeType.startsWith("video/"))) { + callback(Result.success(null)) + return@launch + } + + val localAssetId = extractLocalAssetId(context, uri, mimeType) + val tempFilePath = if (localAssetId == null) { + copyUriToTempFile(context, uri, mimeType)?.absolutePath ?: run { + callback(Result.success(null)) + return@launch + } + } else { + null + } + val payload = ViewIntentPayload( + path = tempFilePath, + mimeType = mimeType, + localAssetId = localAssetId, + ) + consumeViewIntent(intent) + callback(Result.success(payload)) + } catch (e: Exception) { + callback(Result.failure(e)) + } + } + } + + private fun consumeViewIntent(currentIntent: Intent) { + unconsumedIntent = Intent(currentIntent).apply { + action = null + data = null + type = null + } + activity?.intent = unconsumedIntent + } + + private fun extractLocalAssetId(context: Context, uri: Uri, mimeType: String): String? { + return tryExtractDocumentLocalAssetId(context, uri) + ?: tryParseContentUriId(uri) + ?: resolveLocalIdByNameAndSize(context, uri, mimeType) + } + + private fun tryExtractDocumentLocalAssetId(context: Context, uri: Uri): String? { + return try { + if (!DocumentsContract.isDocumentUri(context, uri)) return null + val docId = DocumentsContract.getDocumentId(uri) + if (docId.isBlank() || docId.startsWith("raw:")) return null + docId.substringAfter(':', docId).toLongOrNull()?.toString() + } catch (e: Exception) { + Log.w(TAG, "Failed to resolve local asset id from document URI: $uri", e) + null + } + } + + private fun tryParseContentUriId(uri: Uri): String? { + val id = uri.lastPathSegment?.toLongOrNull() ?: return null + return if (id >= 0) id.toString() else null + } + + private fun copyUriToTempFile(context: Context, uri: Uri, mimeType: String): File? { + return try { + val extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType)?.let { ".$it" } + val tempFile = File.createTempFile("view_intent_", extension, context.cacheDir) + context.contentResolver.openInputStream(uri)?.use { inputStream -> + FileOutputStream(tempFile).use { outputStream -> + inputStream.copyTo(outputStream) + } + } ?: return null + tempFile + } catch (_: Exception) { + null + } + } + + private fun resolveLocalIdByNameAndSize(context: Context, uri: Uri, mimeType: String): String? { + val metaProjection = arrayOf(OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE) + val (displayName, size) = + try { + context.contentResolver.query(uri, metaProjection, null, null, null)?.use { cursor -> + if (!cursor.moveToFirst()) return null + val nameIdx = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME) + val sizeIdx = cursor.getColumnIndex(OpenableColumns.SIZE) + val name = if (nameIdx >= 0) cursor.getString(nameIdx) else null + val bytes = if (sizeIdx >= 0) cursor.getLong(sizeIdx) else -1L + if (name.isNullOrBlank() || bytes < 0) return null + name to bytes + } ?: return null + } catch (_: Exception) { + return null + } + + val tableUri = when { + mimeType.startsWith("image/") -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI + mimeType.startsWith("video/") -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI + else -> return null + } + return try { + context.contentResolver + .query( + tableUri, + arrayOf(MediaStore.MediaColumns._ID), + "${MediaStore.MediaColumns.DISPLAY_NAME}=? AND ${MediaStore.MediaColumns.SIZE}=?", + arrayOf(displayName, size.toString()), + "${MediaStore.MediaColumns.DATE_MODIFIED} DESC", + )?.use { cursor -> + if (!cursor.moveToFirst()) return null + val idIndex = cursor.getColumnIndex(MediaStore.MediaColumns._ID) + if (idIndex < 0) return null + cursor.getLong(idIndex).toString() + } + } catch (_: Exception) { + null + } + } +} diff --git a/mobile/android/fastlane/Fastfile b/mobile/android/fastlane/Fastfile index 7312a8ca68..0f55eeec26 100644 --- a/mobile/android/fastlane/Fastfile +++ b/mobile/android/fastlane/Fastfile @@ -35,8 +35,8 @@ platform :android do task: 'bundle', build_type: 'Release', properties: { - "android.injected.version.code" => 3046, - "android.injected.version.name" => "2.7.5", + "android.injected.version.code" => 3047, + "android.injected.version.name" => "3.0.0", } ) upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab') diff --git a/mobile/android/gradle.properties b/mobile/android/gradle.properties index f63dcd33e1..99f62dc8c2 100644 --- a/mobile/android/gradle.properties +++ b/mobile/android/gradle.properties @@ -5,3 +5,7 @@ android.nonTransitiveRClass=false android.nonFinalResIds=false org.gradle.caching=true org.gradle.parallel=true +# This builtInKotlin flag was added automatically by Flutter migrator +android.builtInKotlin=false +# This newDsl flag was added automatically by Flutter migrator +android.newDsl=false diff --git a/mobile/bin/generate_keys.dart b/mobile/bin/generate_keys.dart index 3c5c284c3e..a4cf562bcb 100644 --- a/mobile/bin/generate_keys.dart +++ b/mobile/bin/generate_keys.dart @@ -217,7 +217,9 @@ List _extractParams(String value) { final icuType = match.group(2)!; final icuContent = match.group(3) ?? ''; - if (params.containsKey(name)) continue; + if (params.containsKey(name)) { + continue; + } String type; if (icuType == 'plural' || icuType == 'number') { @@ -238,7 +240,9 @@ List _extractParams(String value) { for (var i = 0; i < value.length; i++) { if (value[i] == '{') { - if (depth == 0) icuStart = i; + if (depth == 0) { + icuStart = i; + } depth++; } else if (value[i] == '}') { depth--; @@ -256,7 +260,9 @@ List _extractParams(String value) { for (final match in simpleRegex.allMatches(cleanedValue)) { final name = match.group(1)!; - if (params.containsKey(name)) continue; + if (params.containsKey(name)) { + continue; + } String type; if (_kIntParamNames.contains(name.toLowerCase())) { diff --git a/mobile/drift_schemas/main/drift_schema_v25.json b/mobile/drift_schemas/main/drift_schema_v25.json index bef3379d35..5a3f78aae7 100644 --- a/mobile/drift_schemas/main/drift_schema_v25.json +++ b/mobile/drift_schemas/main/drift_schema_v25.json @@ -1003,20 +1003,6 @@ 1 ], "type": "index", - "data": { - "on": 1, - "name": "idx_remote_asset_owner_checksum", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)", - "unique": false, - "columns": [] - } - }, - { - "id": 12, - "references": [ - 1 - ], - "type": "index", "data": { "on": 1, "name": "UQ_remote_assets_owner_checksum", @@ -1026,7 +1012,7 @@ } }, { - "id": 13, + "id": 12, "references": [ 1 ], @@ -1040,7 +1026,7 @@ } }, { - "id": 14, + "id": 13, "references": [ 1 ], @@ -1054,7 +1040,7 @@ } }, { - "id": 15, + "id": 14, "references": [ 1 ], @@ -1067,36 +1053,22 @@ "columns": [] } }, + { + "id": 15, + "references": [ + 1 + ], + "type": "index", + "data": { + "on": 1, + "name": "idx_remote_asset_owner_visibility_deleted_created", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_visibility_deleted_created\nON remote_asset_entity (owner_id, visibility, deleted_at, created_at DESC)\n", + "unique": false, + "columns": [] + } + }, { "id": 16, - "references": [ - 1 - ], - "type": "index", - "data": { - "on": 1, - "name": "idx_remote_asset_local_date_time_day", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_day ON remote_asset_entity (STRFTIME('%Y-%m-%d', local_date_time))", - "unique": false, - "columns": [] - } - }, - { - "id": 17, - "references": [ - 1 - ], - "type": "index", - "data": { - "on": 1, - "name": "idx_remote_asset_local_date_time_month", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_month ON remote_asset_entity (STRFTIME('%Y-%m', local_date_time))", - "unique": false, - "columns": [] - } - }, - { - "id": 18, "references": [], "type": "table", "data": { @@ -1226,7 +1198,7 @@ } }, { - "id": 19, + "id": 17, "references": [ 0 ], @@ -1301,7 +1273,7 @@ } }, { - "id": 20, + "id": 18, "references": [ 0 ], @@ -1388,7 +1360,7 @@ } }, { - "id": 21, + "id": 19, "references": [ 1 ], @@ -1644,7 +1616,7 @@ } }, { - "id": 22, + "id": 20, "references": [ 1, 4 @@ -1718,7 +1690,7 @@ } }, { - "id": 23, + "id": 21, "references": [ 4, 0 @@ -1806,7 +1778,7 @@ } }, { - "id": 24, + "id": 22, "references": [ 1 ], @@ -1902,7 +1874,7 @@ } }, { - "id": 25, + "id": 23, "references": [ 0 ], @@ -2066,10 +2038,10 @@ } }, { - "id": 26, + "id": 24, "references": [ 1, - 25 + 23 ], "type": "table", "data": { @@ -2140,7 +2112,7 @@ } }, { - "id": 27, + "id": 25, "references": [ 0 ], @@ -2284,10 +2256,10 @@ } }, { - "id": 28, + "id": 26, "references": [ 1, - 27 + 25 ], "type": "table", "data": { @@ -2461,7 +2433,7 @@ } }, { - "id": 29, + "id": 27, "references": [], "type": "table", "data": { @@ -2509,7 +2481,7 @@ } }, { - "id": 30, + "id": 28, "references": [], "type": "table", "data": { @@ -2684,7 +2656,7 @@ } }, { - "id": 31, + "id": 29, "references": [ 1 ], @@ -2778,18 +2750,16 @@ } }, { - "id": 32, - "references": [ - 1 - ], + "id": 30, + "references": [], "type": "table", "data": { - "name": "asset_ocr_entity", + "name": "metadata", "was_declared_in_moor": false, "columns": [ { - "name": "id", - "getter_name": "id", + "name": "key", + "getter_name": "key", "moor_type": "string", "nullable": false, "customConstraints": null, @@ -2798,134 +2768,8 @@ "dsl_features": [] }, { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "x1", - "getter_name": "x1", - "moor_type": "double", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "y1", - "getter_name": "y1", - "moor_type": "double", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "x2", - "getter_name": "x2", - "moor_type": "double", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "y2", - "getter_name": "y2", - "moor_type": "double", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "x3", - "getter_name": "x3", - "moor_type": "double", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "y3", - "getter_name": "y3", - "moor_type": "double", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "x4", - "getter_name": "x4", - "moor_type": "double", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "y4", - "getter_name": "y4", - "moor_type": "double", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "box_score", - "getter_name": "boxScore", - "moor_type": "double", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "text_score", - "getter_name": "textScore", - "moor_type": "double", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "recognized_text", - "getter_name": "recognizedText", + "name": "value", + "getter_name": "value", "moor_type": "string", "nullable": false, "customConstraints": null, @@ -2934,16 +2778,12 @@ "dsl_features": [] }, { - "name": "is_visible", - "getter_name": "isVisible", - "moor_type": "bool", + "name": "updated_at", + "getter_name": "updatedAt", + "moor_type": "dateTime", "nullable": false, "customConstraints": null, - "defaultConstraints": "CHECK (\"is_visible\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_visible\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('1')", + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", "default_client_dart": null, "dsl_features": [] } @@ -2953,18 +2793,18 @@ "constraints": [], "strict": true, "explicit_pk": [ - "id" + "key" ] } }, { - "id": 33, + "id": 31, "references": [ - 20 + 18 ], "type": "index", "data": { - "on": 20, + "on": 18, "name": "idx_partner_shared_with_id", "sql": "CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)", "unique": false, @@ -2972,19 +2812,47 @@ } }, { - "id": 34, + "id": 32, "references": [ - 21 + 19 ], "type": "index", "data": { - "on": 21, + "on": 19, "name": "idx_lat_lng", "sql": "CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)", "unique": false, "columns": [] } }, + { + "id": 33, + "references": [ + 19 + ], + "type": "index", + "data": { + "on": 19, + "name": "idx_remote_exif_city", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_exif_city\nON remote_exif_entity (city) WHERE city IS NOT NULL\n", + "unique": false, + "columns": [] + } + }, + { + "id": 34, + "references": [ + 20 + ], + "type": "index", + "data": { + "on": 20, + "name": "idx_remote_album_asset_album_asset", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)", + "unique": false, + "columns": [] + } + }, { "id": 35, "references": [ @@ -2993,20 +2861,6 @@ "type": "index", "data": { "on": 22, - "name": "idx_remote_album_asset_album_asset", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 36, - "references": [ - 24 - ], - "type": "index", - "data": { - "on": 24, "name": "idx_remote_asset_cloud_id", "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)", "unique": false, @@ -3014,13 +2868,13 @@ } }, { - "id": 37, + "id": 36, "references": [ - 27 + 25 ], "type": "index", "data": { - "on": 27, + "on": 25, "name": "idx_person_owner_id", "sql": "CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)", "unique": false, @@ -3028,13 +2882,13 @@ } }, { - "id": 38, + "id": 37, "references": [ - 28 + 26 ], "type": "index", "data": { - "on": 28, + "on": 26, "name": "idx_asset_face_person_id", "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)", "unique": false, @@ -3042,13 +2896,13 @@ } }, { - "id": 39, + "id": 38, "references": [ - 28 + 26 ], "type": "index", "data": { - "on": 28, + "on": 26, "name": "idx_asset_face_asset_id", "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)", "unique": false, @@ -3056,13 +2910,27 @@ } }, { - "id": 40, + "id": 39, "references": [ - 30 + 26 ], "type": "index", "data": { - "on": 30, + "on": 26, + "name": "idx_asset_face_visible_person", + "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_visible_person\nON asset_face_entity (person_id, asset_id)\nWHERE is_visible = 1 AND deleted_at IS NULL\n", + "unique": false, + "columns": [] + } + }, + { + "id": 40, + "references": [ + 28 + ], + "type": "index", + "data": { + "on": 28, "name": "idx_trashed_local_asset_checksum", "sql": "CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)", "unique": false, @@ -3072,11 +2940,11 @@ { "id": 41, "references": [ - 30 + 28 ], "type": "index", "data": { - "on": 30, + "on": 28, "name": "idx_trashed_local_asset_album", "sql": "CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)", "unique": false, @@ -3086,11 +2954,11 @@ { "id": 42, "references": [ - 31 + 29 ], "type": "index", "data": { - "on": 31, + "on": 29, "name": "idx_asset_edit_asset_id", "sql": "CREATE INDEX IF NOT EXISTS idx_asset_edit_asset_id ON asset_edit_entity (asset_id)", "unique": false, @@ -3198,15 +3066,6 @@ } ] }, - { - "name": "idx_remote_asset_owner_checksum", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)" - } - ] - }, { "name": "UQ_remote_assets_owner_checksum", "sql": [ @@ -3244,20 +3103,11 @@ ] }, { - "name": "idx_remote_asset_local_date_time_day", + "name": "idx_remote_asset_owner_visibility_deleted_created", "sql": [ { "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_day ON remote_asset_entity (STRFTIME('%Y-%m-%d', local_date_time))" - } - ] - }, - { - "name": "idx_remote_asset_local_date_time_month", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_month ON remote_asset_entity (STRFTIME('%Y-%m', local_date_time))" + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_visibility_deleted_created ON remote_asset_entity (owner_id, visibility, deleted_at, created_at DESC)" } ] }, @@ -3388,11 +3238,11 @@ ] }, { - "name": "asset_ocr_entity", + "name": "metadata", "sql": [ { "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"asset_ocr_entity\" (\"id\" TEXT NOT NULL, \"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"x1\" REAL NOT NULL, \"y1\" REAL NOT NULL, \"x2\" REAL NOT NULL, \"y2\" REAL NOT NULL, \"x3\" REAL NOT NULL, \"y3\" REAL NOT NULL, \"x4\" REAL NOT NULL, \"y4\" REAL NOT NULL, \"box_score\" REAL NOT NULL, \"text_score\" REAL NOT NULL, \"recognized_text\" TEXT NOT NULL, \"is_visible\" INTEGER NOT NULL DEFAULT 1 CHECK (\"is_visible\" IN (0, 1)), PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + "sql": "CREATE TABLE IF NOT EXISTS \"metadata\" (\"key\" TEXT NOT NULL, \"value\" TEXT NOT NULL, \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), PRIMARY KEY (\"key\")) WITHOUT ROWID, STRICT;" } ] }, @@ -3414,6 +3264,15 @@ } ] }, + { + "name": "idx_remote_exif_city", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_exif_city ON remote_exif_entity (city) WHERE city IS NOT NULL" + } + ] + }, { "name": "idx_remote_album_asset_album_asset", "sql": [ @@ -3459,6 +3318,15 @@ } ] }, + { + "name": "idx_asset_face_visible_person", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_visible_person ON asset_face_entity (person_id, asset_id) WHERE is_visible = 1 AND deleted_at IS NULL" + } + ] + }, { "name": "idx_trashed_local_asset_checksum", "sql": [ diff --git a/mobile/drift_schemas/main/drift_schema_v26.json b/mobile/drift_schemas/main/drift_schema_v26.json new file mode 100644 index 0000000000..b958bcca43 --- /dev/null +++ b/mobile/drift_schemas/main/drift_schema_v26.json @@ -0,0 +1,3368 @@ +{ + "_meta": { + "description": "This file contains a serialized version of schema entities for drift.", + "version": "1.3.0" + }, + "options": { + "store_date_time_values_as_text": true + }, + "entities": [ + { + "id": 0, + "references": [], + "type": "table", + "data": { + "name": "user_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "name", + "getter_name": "name", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "email", + "getter_name": "email", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "has_profile_image", + "getter_name": "hasProfileImage", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"has_profile_image\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"has_profile_image\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "profile_changed_at", + "getter_name": "profileChangedAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "avatar_color", + "getter_name": "avatarColor", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(AvatarColor.values)", + "dart_type_name": "AvatarColor" + } + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 1, + "references": [ + 0 + ], + "type": "table", + "data": { + "name": "remote_asset_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "name", + "getter_name": "name", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "type", + "getter_name": "type", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(AssetType.values)", + "dart_type_name": "AssetType" + } + }, + { + "name": "created_at", + "getter_name": "createdAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "updated_at", + "getter_name": "updatedAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "width", + "getter_name": "width", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "height", + "getter_name": "height", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "duration_ms", + "getter_name": "durationMs", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "checksum", + "getter_name": "checksum", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "is_favorite", + "getter_name": "isFavorite", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"is_favorite\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"is_favorite\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "owner_id", + "getter_name": "ownerId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "user_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "local_date_time", + "getter_name": "localDateTime", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "thumb_hash", + "getter_name": "thumbHash", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "deleted_at", + "getter_name": "deletedAt", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "uploaded_at", + "getter_name": "uploadedAt", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "live_photo_video_id", + "getter_name": "livePhotoVideoId", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "visibility", + "getter_name": "visibility", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(AssetVisibility.values)", + "dart_type_name": "AssetVisibility" + } + }, + { + "name": "stack_id", + "getter_name": "stackId", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "library_id", + "getter_name": "libraryId", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "is_edited", + "getter_name": "isEdited", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"is_edited\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"is_edited\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 2, + "references": [ + 0 + ], + "type": "table", + "data": { + "name": "stack_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "created_at", + "getter_name": "createdAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "updated_at", + "getter_name": "updatedAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "owner_id", + "getter_name": "ownerId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "user_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "primary_asset_id", + "getter_name": "primaryAssetId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 3, + "references": [], + "type": "table", + "data": { + "name": "local_asset_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "name", + "getter_name": "name", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "type", + "getter_name": "type", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(AssetType.values)", + "dart_type_name": "AssetType" + } + }, + { + "name": "created_at", + "getter_name": "createdAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "updated_at", + "getter_name": "updatedAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "width", + "getter_name": "width", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "height", + "getter_name": "height", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "duration_ms", + "getter_name": "durationMs", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "checksum", + "getter_name": "checksum", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "is_favorite", + "getter_name": "isFavorite", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"is_favorite\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"is_favorite\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "orientation", + "getter_name": "orientation", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "i_cloud_id", + "getter_name": "iCloudId", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "adjustment_time", + "getter_name": "adjustmentTime", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "latitude", + "getter_name": "latitude", + "moor_type": "double", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "longitude", + "getter_name": "longitude", + "moor_type": "double", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "playback_style", + "getter_name": "playbackStyle", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(AssetPlaybackStyle.values)", + "dart_type_name": "AssetPlaybackStyle" + } + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 4, + "references": [ + 1 + ], + "type": "table", + "data": { + "name": "remote_album_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "name", + "getter_name": "name", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "description", + "getter_name": "description", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('\\'\\'')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "created_at", + "getter_name": "createdAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "updated_at", + "getter_name": "updatedAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "thumbnail_asset_id", + "getter_name": "thumbnailAssetId", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE SET NULL", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE SET NULL" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "remote_asset_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "setNull" + } + } + ] + }, + { + "name": "is_activity_enabled", + "getter_name": "isActivityEnabled", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"is_activity_enabled\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"is_activity_enabled\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('1')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "order", + "getter_name": "order", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(AlbumAssetOrder.values)", + "dart_type_name": "AlbumAssetOrder" + } + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 5, + "references": [ + 4 + ], + "type": "table", + "data": { + "name": "local_album_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "name", + "getter_name": "name", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "updated_at", + "getter_name": "updatedAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "backup_selection", + "getter_name": "backupSelection", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(BackupSelection.values)", + "dart_type_name": "BackupSelection" + } + }, + { + "name": "is_ios_shared_album", + "getter_name": "isIosSharedAlbum", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"is_ios_shared_album\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"is_ios_shared_album\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "linked_remote_album_id", + "getter_name": "linkedRemoteAlbumId", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "defaultConstraints": "REFERENCES remote_album_entity (id) ON DELETE SET NULL", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES remote_album_entity (id) ON DELETE SET NULL" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "remote_album_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "setNull" + } + } + ] + }, + { + "name": "marker", + "getter_name": "marker_", + "moor_type": "bool", + "nullable": true, + "customConstraints": null, + "defaultConstraints": "CHECK (\"marker\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"marker\" IN (0, 1))" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 6, + "references": [ + 3, + 5 + ], + "type": "table", + "data": { + "name": "local_album_asset_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "asset_id", + "getter_name": "assetId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES local_asset_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES local_asset_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "local_asset_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "album_id", + "getter_name": "albumId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES local_album_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES local_album_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "local_album_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "marker", + "getter_name": "marker_", + "moor_type": "bool", + "nullable": true, + "customConstraints": null, + "defaultConstraints": "CHECK (\"marker\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"marker\" IN (0, 1))" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "asset_id", + "album_id" + ] + } + }, + { + "id": 7, + "references": [ + 6 + ], + "type": "index", + "data": { + "on": 6, + "name": "idx_local_album_asset_album_asset", + "sql": "CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 8, + "references": [ + 3 + ], + "type": "index", + "data": { + "on": 3, + "name": "idx_local_asset_checksum", + "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)", + "unique": false, + "columns": [] + } + }, + { + "id": 9, + "references": [ + 3 + ], + "type": "index", + "data": { + "on": 3, + "name": "idx_local_asset_cloud_id", + "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 10, + "references": [ + 2 + ], + "type": "index", + "data": { + "on": 2, + "name": "idx_stack_primary_asset_id", + "sql": "CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 11, + "references": [ + 1 + ], + "type": "index", + "data": { + "on": 1, + "name": "UQ_remote_assets_owner_checksum", + "sql": "CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum\nON remote_asset_entity (owner_id, checksum)\nWHERE (library_id IS NULL);\n", + "unique": true, + "columns": [] + } + }, + { + "id": 12, + "references": [ + 1 + ], + "type": "index", + "data": { + "on": 1, + "name": "UQ_remote_assets_owner_library_checksum", + "sql": "CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum\nON remote_asset_entity (owner_id, library_id, checksum)\nWHERE (library_id IS NOT NULL);\n", + "unique": true, + "columns": [] + } + }, + { + "id": 13, + "references": [ + 1 + ], + "type": "index", + "data": { + "on": 1, + "name": "idx_remote_asset_checksum", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)", + "unique": false, + "columns": [] + } + }, + { + "id": 14, + "references": [ + 1 + ], + "type": "index", + "data": { + "on": 1, + "name": "idx_remote_asset_stack_id", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 15, + "references": [ + 1 + ], + "type": "index", + "data": { + "on": 1, + "name": "idx_remote_asset_owner_visibility_deleted_created", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_visibility_deleted_created\nON remote_asset_entity (owner_id, visibility, deleted_at, created_at DESC)\n", + "unique": false, + "columns": [] + } + }, + { + "id": 16, + "references": [], + "type": "table", + "data": { + "name": "auth_user_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "name", + "getter_name": "name", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "email", + "getter_name": "email", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "is_admin", + "getter_name": "isAdmin", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"is_admin\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"is_admin\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "has_profile_image", + "getter_name": "hasProfileImage", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"has_profile_image\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"has_profile_image\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "profile_changed_at", + "getter_name": "profileChangedAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "avatar_color", + "getter_name": "avatarColor", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(AvatarColor.values)", + "dart_type_name": "AvatarColor" + } + }, + { + "name": "quota_size_in_bytes", + "getter_name": "quotaSizeInBytes", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "quota_usage_in_bytes", + "getter_name": "quotaUsageInBytes", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "pin_code", + "getter_name": "pinCode", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 17, + "references": [ + 0 + ], + "type": "table", + "data": { + "name": "user_metadata_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "user_id", + "getter_name": "userId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "user_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "key", + "getter_name": "key", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(UserMetadataKey.values)", + "dart_type_name": "UserMetadataKey" + } + }, + { + "name": "value", + "getter_name": "value", + "moor_type": "blob", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "userMetadataConverter", + "dart_type_name": "Map" + } + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "user_id", + "key" + ] + } + }, + { + "id": 18, + "references": [ + 0 + ], + "type": "table", + "data": { + "name": "partner_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "shared_by_id", + "getter_name": "sharedById", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "user_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "shared_with_id", + "getter_name": "sharedWithId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "user_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "in_timeline", + "getter_name": "inTimeline", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"in_timeline\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"in_timeline\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "shared_by_id", + "shared_with_id" + ] + } + }, + { + "id": 19, + "references": [ + 1 + ], + "type": "table", + "data": { + "name": "remote_exif_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "asset_id", + "getter_name": "assetId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "remote_asset_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "city", + "getter_name": "city", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "state", + "getter_name": "state", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "country", + "getter_name": "country", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "date_time_original", + "getter_name": "dateTimeOriginal", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "description", + "getter_name": "description", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "height", + "getter_name": "height", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "width", + "getter_name": "width", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "exposure_time", + "getter_name": "exposureTime", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "f_number", + "getter_name": "fNumber", + "moor_type": "double", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "file_size", + "getter_name": "fileSize", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "focal_length", + "getter_name": "focalLength", + "moor_type": "double", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "latitude", + "getter_name": "latitude", + "moor_type": "double", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "longitude", + "getter_name": "longitude", + "moor_type": "double", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "iso", + "getter_name": "iso", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "make", + "getter_name": "make", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "model", + "getter_name": "model", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "lens", + "getter_name": "lens", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "orientation", + "getter_name": "orientation", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "time_zone", + "getter_name": "timeZone", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "rating", + "getter_name": "rating", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "projection_type", + "getter_name": "projectionType", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "asset_id" + ] + } + }, + { + "id": 20, + "references": [ + 1, + 4 + ], + "type": "table", + "data": { + "name": "remote_album_asset_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "asset_id", + "getter_name": "assetId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "remote_asset_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "album_id", + "getter_name": "albumId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES remote_album_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES remote_album_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "remote_album_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "asset_id", + "album_id" + ] + } + }, + { + "id": 21, + "references": [ + 4, + 0 + ], + "type": "table", + "data": { + "name": "remote_album_user_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "album_id", + "getter_name": "albumId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES remote_album_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES remote_album_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "remote_album_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "user_id", + "getter_name": "userId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "user_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "role", + "getter_name": "role", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(AlbumUserRole.values)", + "dart_type_name": "AlbumUserRole" + } + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "album_id", + "user_id" + ] + } + }, + { + "id": 22, + "references": [ + 1 + ], + "type": "table", + "data": { + "name": "remote_asset_cloud_id_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "asset_id", + "getter_name": "assetId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "remote_asset_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "cloud_id", + "getter_name": "cloudId", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "created_at", + "getter_name": "createdAt", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "adjustment_time", + "getter_name": "adjustmentTime", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "latitude", + "getter_name": "latitude", + "moor_type": "double", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "longitude", + "getter_name": "longitude", + "moor_type": "double", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "asset_id" + ] + } + }, + { + "id": 23, + "references": [ + 0 + ], + "type": "table", + "data": { + "name": "memory_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "created_at", + "getter_name": "createdAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "updated_at", + "getter_name": "updatedAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "deleted_at", + "getter_name": "deletedAt", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "owner_id", + "getter_name": "ownerId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "user_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "type", + "getter_name": "type", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(MemoryTypeEnum.values)", + "dart_type_name": "MemoryTypeEnum" + } + }, + { + "name": "data", + "getter_name": "data", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "is_saved", + "getter_name": "isSaved", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"is_saved\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"is_saved\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "memory_at", + "getter_name": "memoryAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "seen_at", + "getter_name": "seenAt", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "show_at", + "getter_name": "showAt", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "hide_at", + "getter_name": "hideAt", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 24, + "references": [ + 1, + 23 + ], + "type": "table", + "data": { + "name": "memory_asset_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "asset_id", + "getter_name": "assetId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "remote_asset_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "memory_id", + "getter_name": "memoryId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES memory_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES memory_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "memory_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "asset_id", + "memory_id" + ] + } + }, + { + "id": 25, + "references": [ + 0 + ], + "type": "table", + "data": { + "name": "person_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "created_at", + "getter_name": "createdAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "updated_at", + "getter_name": "updatedAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "owner_id", + "getter_name": "ownerId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "user_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "name", + "getter_name": "name", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "face_asset_id", + "getter_name": "faceAssetId", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "is_favorite", + "getter_name": "isFavorite", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"is_favorite\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"is_favorite\" IN (0, 1))" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "is_hidden", + "getter_name": "isHidden", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"is_hidden\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"is_hidden\" IN (0, 1))" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "color", + "getter_name": "color", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "birth_date", + "getter_name": "birthDate", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 26, + "references": [ + 1, + 25 + ], + "type": "table", + "data": { + "name": "asset_face_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "asset_id", + "getter_name": "assetId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "remote_asset_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "person_id", + "getter_name": "personId", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "defaultConstraints": "REFERENCES person_entity (id) ON DELETE SET NULL", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES person_entity (id) ON DELETE SET NULL" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "person_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "setNull" + } + } + ] + }, + { + "name": "image_width", + "getter_name": "imageWidth", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "image_height", + "getter_name": "imageHeight", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "bounding_box_x1", + "getter_name": "boundingBoxX1", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "bounding_box_y1", + "getter_name": "boundingBoxY1", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "bounding_box_x2", + "getter_name": "boundingBoxX2", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "bounding_box_y2", + "getter_name": "boundingBoxY2", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "source_type", + "getter_name": "sourceType", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "is_visible", + "getter_name": "isVisible", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"is_visible\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"is_visible\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('1')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "deleted_at", + "getter_name": "deletedAt", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 27, + "references": [], + "type": "table", + "data": { + "name": "store_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "string_value", + "getter_name": "stringValue", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "int_value", + "getter_name": "intValue", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 28, + "references": [], + "type": "table", + "data": { + "name": "trashed_local_asset_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "name", + "getter_name": "name", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "type", + "getter_name": "type", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(AssetType.values)", + "dart_type_name": "AssetType" + } + }, + { + "name": "created_at", + "getter_name": "createdAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "updated_at", + "getter_name": "updatedAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "width", + "getter_name": "width", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "height", + "getter_name": "height", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "duration_ms", + "getter_name": "durationMs", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "album_id", + "getter_name": "albumId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "checksum", + "getter_name": "checksum", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "is_favorite", + "getter_name": "isFavorite", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"is_favorite\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"is_favorite\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "orientation", + "getter_name": "orientation", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "source", + "getter_name": "source", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(TrashOrigin.values)", + "dart_type_name": "TrashOrigin" + } + }, + { + "name": "playback_style", + "getter_name": "playbackStyle", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(AssetPlaybackStyle.values)", + "dart_type_name": "AssetPlaybackStyle" + } + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id", + "album_id" + ] + } + }, + { + "id": 29, + "references": [ + 1 + ], + "type": "table", + "data": { + "name": "asset_edit_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "asset_id", + "getter_name": "assetId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "remote_asset_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "action", + "getter_name": "action", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(AssetEditAction.values)", + "dart_type_name": "AssetEditAction" + } + }, + { + "name": "parameters", + "getter_name": "parameters", + "moor_type": "blob", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "editParameterConverter", + "dart_type_name": "Map" + } + }, + { + "name": "sequence", + "getter_name": "sequence", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 30, + "references": [], + "type": "table", + "data": { + "name": "metadata", + "was_declared_in_moor": false, + "columns": [ + { + "name": "key", + "getter_name": "key", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "value", + "getter_name": "value", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "updated_at", + "getter_name": "updatedAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "key" + ] + } + }, + { + "id": 31, + "references": [ + 18 + ], + "type": "index", + "data": { + "on": 18, + "name": "idx_partner_shared_with_id", + "sql": "CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 32, + "references": [ + 19 + ], + "type": "index", + "data": { + "on": 19, + "name": "idx_lat_lng", + "sql": "CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)", + "unique": false, + "columns": [] + } + }, + { + "id": 33, + "references": [ + 19 + ], + "type": "index", + "data": { + "on": 19, + "name": "idx_remote_exif_city", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_exif_city\nON remote_exif_entity (city) WHERE city IS NOT NULL\n", + "unique": false, + "columns": [] + } + }, + { + "id": 34, + "references": [ + 20 + ], + "type": "index", + "data": { + "on": 20, + "name": "idx_remote_album_asset_album_asset", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 35, + "references": [ + 22 + ], + "type": "index", + "data": { + "on": 22, + "name": "idx_remote_asset_cloud_id", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 36, + "references": [ + 25 + ], + "type": "index", + "data": { + "on": 25, + "name": "idx_person_owner_id", + "sql": "CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 37, + "references": [ + 26 + ], + "type": "index", + "data": { + "on": 26, + "name": "idx_asset_face_person_id", + "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 38, + "references": [ + 26 + ], + "type": "index", + "data": { + "on": 26, + "name": "idx_asset_face_asset_id", + "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 39, + "references": [ + 26 + ], + "type": "index", + "data": { + "on": 26, + "name": "idx_asset_face_visible_person", + "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_visible_person\nON asset_face_entity (person_id, asset_id)\nWHERE is_visible = 1 AND deleted_at IS NULL\n", + "unique": false, + "columns": [] + } + }, + { + "id": 40, + "references": [ + 28 + ], + "type": "index", + "data": { + "on": 28, + "name": "idx_trashed_local_asset_checksum", + "sql": "CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)", + "unique": false, + "columns": [] + } + }, + { + "id": 41, + "references": [ + 28 + ], + "type": "index", + "data": { + "on": 28, + "name": "idx_trashed_local_asset_album", + "sql": "CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 42, + "references": [ + 29 + ], + "type": "index", + "data": { + "on": 29, + "name": "idx_asset_edit_asset_id", + "sql": "CREATE INDEX IF NOT EXISTS idx_asset_edit_asset_id ON asset_edit_entity (asset_id)", + "unique": false, + "columns": [] + } + } + ], + "fixed_sql": [ + { + "name": "user_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"user_entity\" (\"id\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"email\" TEXT NOT NULL, \"has_profile_image\" INTEGER NOT NULL DEFAULT 0 CHECK (\"has_profile_image\" IN (0, 1)), \"profile_changed_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"avatar_color\" INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "remote_asset_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"remote_asset_entity\" (\"name\" TEXT NOT NULL, \"type\" INTEGER NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"width\" INTEGER NULL, \"height\" INTEGER NULL, \"duration_ms\" INTEGER NULL, \"id\" TEXT NOT NULL, \"checksum\" TEXT NOT NULL, \"is_favorite\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_favorite\" IN (0, 1)), \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"local_date_time\" TEXT NULL, \"thumb_hash\" TEXT NULL, \"deleted_at\" TEXT NULL, \"uploaded_at\" TEXT NULL, \"live_photo_video_id\" TEXT NULL, \"visibility\" INTEGER NOT NULL, \"stack_id\" TEXT NULL, \"library_id\" TEXT NULL, \"is_edited\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_edited\" IN (0, 1)), PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "stack_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"stack_entity\" (\"id\" TEXT NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"primary_asset_id\" TEXT NOT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "local_asset_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"local_asset_entity\" (\"name\" TEXT NOT NULL, \"type\" INTEGER NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"width\" INTEGER NULL, \"height\" INTEGER NULL, \"duration_ms\" INTEGER NULL, \"id\" TEXT NOT NULL, \"checksum\" TEXT NULL, \"is_favorite\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_favorite\" IN (0, 1)), \"orientation\" INTEGER NOT NULL DEFAULT 0, \"i_cloud_id\" TEXT NULL, \"adjustment_time\" TEXT NULL, \"latitude\" REAL NULL, \"longitude\" REAL NULL, \"playback_style\" INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "remote_album_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"remote_album_entity\" (\"id\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"description\" TEXT NOT NULL DEFAULT '', \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"thumbnail_asset_id\" TEXT NULL REFERENCES remote_asset_entity (id) ON DELETE SET NULL, \"is_activity_enabled\" INTEGER NOT NULL DEFAULT 1 CHECK (\"is_activity_enabled\" IN (0, 1)), \"order\" INTEGER NOT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "local_album_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"local_album_entity\" (\"id\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"backup_selection\" INTEGER NOT NULL, \"is_ios_shared_album\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_ios_shared_album\" IN (0, 1)), \"linked_remote_album_id\" TEXT NULL REFERENCES remote_album_entity (id) ON DELETE SET NULL, \"marker\" INTEGER NULL CHECK (\"marker\" IN (0, 1)), PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "local_album_asset_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"local_album_asset_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES local_asset_entity (id) ON DELETE CASCADE, \"album_id\" TEXT NOT NULL REFERENCES local_album_entity (id) ON DELETE CASCADE, \"marker\" INTEGER NULL CHECK (\"marker\" IN (0, 1)), PRIMARY KEY (\"asset_id\", \"album_id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "idx_local_album_asset_album_asset", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)" + } + ] + }, + { + "name": "idx_local_asset_checksum", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)" + } + ] + }, + { + "name": "idx_local_asset_cloud_id", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)" + } + ] + }, + { + "name": "idx_stack_primary_asset_id", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)" + } + ] + }, + { + "name": "UQ_remote_assets_owner_checksum", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)" + } + ] + }, + { + "name": "UQ_remote_assets_owner_library_checksum", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)" + } + ] + }, + { + "name": "idx_remote_asset_checksum", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)" + } + ] + }, + { + "name": "idx_remote_asset_stack_id", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)" + } + ] + }, + { + "name": "idx_remote_asset_owner_visibility_deleted_created", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_visibility_deleted_created ON remote_asset_entity (owner_id, visibility, deleted_at, created_at DESC)" + } + ] + }, + { + "name": "auth_user_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"auth_user_entity\" (\"id\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"email\" TEXT NOT NULL, \"is_admin\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_admin\" IN (0, 1)), \"has_profile_image\" INTEGER NOT NULL DEFAULT 0 CHECK (\"has_profile_image\" IN (0, 1)), \"profile_changed_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"avatar_color\" INTEGER NOT NULL, \"quota_size_in_bytes\" INTEGER NOT NULL DEFAULT 0, \"quota_usage_in_bytes\" INTEGER NOT NULL DEFAULT 0, \"pin_code\" TEXT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "user_metadata_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"user_metadata_entity\" (\"user_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"key\" INTEGER NOT NULL, \"value\" BLOB NOT NULL, PRIMARY KEY (\"user_id\", \"key\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "partner_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"partner_entity\" (\"shared_by_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"shared_with_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"in_timeline\" INTEGER NOT NULL DEFAULT 0 CHECK (\"in_timeline\" IN (0, 1)), PRIMARY KEY (\"shared_by_id\", \"shared_with_id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "remote_exif_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"remote_exif_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"city\" TEXT NULL, \"state\" TEXT NULL, \"country\" TEXT NULL, \"date_time_original\" TEXT NULL, \"description\" TEXT NULL, \"height\" INTEGER NULL, \"width\" INTEGER NULL, \"exposure_time\" TEXT NULL, \"f_number\" REAL NULL, \"file_size\" INTEGER NULL, \"focal_length\" REAL NULL, \"latitude\" REAL NULL, \"longitude\" REAL NULL, \"iso\" INTEGER NULL, \"make\" TEXT NULL, \"model\" TEXT NULL, \"lens\" TEXT NULL, \"orientation\" TEXT NULL, \"time_zone\" TEXT NULL, \"rating\" INTEGER NULL, \"projection_type\" TEXT NULL, PRIMARY KEY (\"asset_id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "remote_album_asset_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"remote_album_asset_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"album_id\" TEXT NOT NULL REFERENCES remote_album_entity (id) ON DELETE CASCADE, PRIMARY KEY (\"asset_id\", \"album_id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "remote_album_user_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"remote_album_user_entity\" (\"album_id\" TEXT NOT NULL REFERENCES remote_album_entity (id) ON DELETE CASCADE, \"user_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"role\" INTEGER NOT NULL, PRIMARY KEY (\"album_id\", \"user_id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "remote_asset_cloud_id_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"remote_asset_cloud_id_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"cloud_id\" TEXT NULL, \"created_at\" TEXT NULL, \"adjustment_time\" TEXT NULL, \"latitude\" REAL NULL, \"longitude\" REAL NULL, PRIMARY KEY (\"asset_id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "memory_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"memory_entity\" (\"id\" TEXT NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"deleted_at\" TEXT NULL, \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"type\" INTEGER NOT NULL, \"data\" TEXT NOT NULL, \"is_saved\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_saved\" IN (0, 1)), \"memory_at\" TEXT NOT NULL, \"seen_at\" TEXT NULL, \"show_at\" TEXT NULL, \"hide_at\" TEXT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "memory_asset_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"memory_asset_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"memory_id\" TEXT NOT NULL REFERENCES memory_entity (id) ON DELETE CASCADE, PRIMARY KEY (\"asset_id\", \"memory_id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "person_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"person_entity\" (\"id\" TEXT NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"name\" TEXT NOT NULL, \"face_asset_id\" TEXT NULL, \"is_favorite\" INTEGER NOT NULL CHECK (\"is_favorite\" IN (0, 1)), \"is_hidden\" INTEGER NOT NULL CHECK (\"is_hidden\" IN (0, 1)), \"color\" TEXT NULL, \"birth_date\" TEXT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "asset_face_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"asset_face_entity\" (\"id\" TEXT NOT NULL, \"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"person_id\" TEXT NULL REFERENCES person_entity (id) ON DELETE SET NULL, \"image_width\" INTEGER NOT NULL, \"image_height\" INTEGER NOT NULL, \"bounding_box_x1\" INTEGER NOT NULL, \"bounding_box_y1\" INTEGER NOT NULL, \"bounding_box_x2\" INTEGER NOT NULL, \"bounding_box_y2\" INTEGER NOT NULL, \"source_type\" TEXT NOT NULL, \"is_visible\" INTEGER NOT NULL DEFAULT 1 CHECK (\"is_visible\" IN (0, 1)), \"deleted_at\" TEXT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "store_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"store_entity\" (\"id\" INTEGER NOT NULL, \"string_value\" TEXT NULL, \"int_value\" INTEGER NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "trashed_local_asset_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"trashed_local_asset_entity\" (\"name\" TEXT NOT NULL, \"type\" INTEGER NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"width\" INTEGER NULL, \"height\" INTEGER NULL, \"duration_ms\" INTEGER NULL, \"id\" TEXT NOT NULL, \"album_id\" TEXT NOT NULL, \"checksum\" TEXT NULL, \"is_favorite\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_favorite\" IN (0, 1)), \"orientation\" INTEGER NOT NULL DEFAULT 0, \"source\" INTEGER NOT NULL, \"playback_style\" INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (\"id\", \"album_id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "asset_edit_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"asset_edit_entity\" (\"id\" TEXT NOT NULL, \"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"action\" INTEGER NOT NULL, \"parameters\" BLOB NOT NULL, \"sequence\" INTEGER NOT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "metadata", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"metadata\" (\"key\" TEXT NOT NULL, \"value\" TEXT NOT NULL, \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), PRIMARY KEY (\"key\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "idx_partner_shared_with_id", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)" + } + ] + }, + { + "name": "idx_lat_lng", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)" + } + ] + }, + { + "name": "idx_remote_exif_city", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_exif_city ON remote_exif_entity (city) WHERE city IS NOT NULL" + } + ] + }, + { + "name": "idx_remote_album_asset_album_asset", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)" + } + ] + }, + { + "name": "idx_remote_asset_cloud_id", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)" + } + ] + }, + { + "name": "idx_person_owner_id", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)" + } + ] + }, + { + "name": "idx_asset_face_person_id", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)" + } + ] + }, + { + "name": "idx_asset_face_asset_id", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)" + } + ] + }, + { + "name": "idx_asset_face_visible_person", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_visible_person ON asset_face_entity (person_id, asset_id) WHERE is_visible = 1 AND deleted_at IS NULL" + } + ] + }, + { + "name": "idx_trashed_local_asset_checksum", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)" + } + ] + }, + { + "name": "idx_trashed_local_asset_album", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)" + } + ] + }, + { + "name": "idx_asset_edit_asset_id", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_asset_edit_asset_id ON asset_edit_entity (asset_id)" + } + ] + } + ] +} \ No newline at end of file diff --git a/mobile/drift_schemas/main/drift_schema_v27.json b/mobile/drift_schemas/main/drift_schema_v27.json new file mode 100644 index 0000000000..4df6ef8389 --- /dev/null +++ b/mobile/drift_schemas/main/drift_schema_v27.json @@ -0,0 +1,3368 @@ +{ + "_meta": { + "description": "This file contains a serialized version of schema entities for drift.", + "version": "1.3.0" + }, + "options": { + "store_date_time_values_as_text": true + }, + "entities": [ + { + "id": 0, + "references": [], + "type": "table", + "data": { + "name": "user_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "name", + "getter_name": "name", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "email", + "getter_name": "email", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "has_profile_image", + "getter_name": "hasProfileImage", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"has_profile_image\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"has_profile_image\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "profile_changed_at", + "getter_name": "profileChangedAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "avatar_color", + "getter_name": "avatarColor", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(AvatarColor.values)", + "dart_type_name": "AvatarColor" + } + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 1, + "references": [ + 0 + ], + "type": "table", + "data": { + "name": "remote_asset_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "name", + "getter_name": "name", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "type", + "getter_name": "type", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(AssetType.values)", + "dart_type_name": "AssetType" + } + }, + { + "name": "created_at", + "getter_name": "createdAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "updated_at", + "getter_name": "updatedAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "width", + "getter_name": "width", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "height", + "getter_name": "height", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "duration_ms", + "getter_name": "durationMs", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "checksum", + "getter_name": "checksum", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "is_favorite", + "getter_name": "isFavorite", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"is_favorite\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"is_favorite\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "owner_id", + "getter_name": "ownerId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "user_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "local_date_time", + "getter_name": "localDateTime", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "thumb_hash", + "getter_name": "thumbHash", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "deleted_at", + "getter_name": "deletedAt", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "uploaded_at", + "getter_name": "uploadedAt", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "live_photo_video_id", + "getter_name": "livePhotoVideoId", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "visibility", + "getter_name": "visibility", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(AssetVisibility.values)", + "dart_type_name": "AssetVisibility" + } + }, + { + "name": "stack_id", + "getter_name": "stackId", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "library_id", + "getter_name": "libraryId", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "is_edited", + "getter_name": "isEdited", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"is_edited\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"is_edited\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 2, + "references": [ + 0 + ], + "type": "table", + "data": { + "name": "stack_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "created_at", + "getter_name": "createdAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "updated_at", + "getter_name": "updatedAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "owner_id", + "getter_name": "ownerId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "user_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "primary_asset_id", + "getter_name": "primaryAssetId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 3, + "references": [], + "type": "table", + "data": { + "name": "local_asset_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "name", + "getter_name": "name", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "type", + "getter_name": "type", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(AssetType.values)", + "dart_type_name": "AssetType" + } + }, + { + "name": "created_at", + "getter_name": "createdAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "updated_at", + "getter_name": "updatedAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "width", + "getter_name": "width", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "height", + "getter_name": "height", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "duration_ms", + "getter_name": "durationMs", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "checksum", + "getter_name": "checksum", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "is_favorite", + "getter_name": "isFavorite", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"is_favorite\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"is_favorite\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "orientation", + "getter_name": "orientation", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "i_cloud_id", + "getter_name": "iCloudId", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "adjustment_time", + "getter_name": "adjustmentTime", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "latitude", + "getter_name": "latitude", + "moor_type": "double", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "longitude", + "getter_name": "longitude", + "moor_type": "double", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "playback_style", + "getter_name": "playbackStyle", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(AssetPlaybackStyle.values)", + "dart_type_name": "AssetPlaybackStyle" + } + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 4, + "references": [ + 1 + ], + "type": "table", + "data": { + "name": "remote_album_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "name", + "getter_name": "name", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "description", + "getter_name": "description", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('\\'\\'')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "created_at", + "getter_name": "createdAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "updated_at", + "getter_name": "updatedAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "thumbnail_asset_id", + "getter_name": "thumbnailAssetId", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE SET NULL", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE SET NULL" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "remote_asset_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "setNull" + } + } + ] + }, + { + "name": "is_activity_enabled", + "getter_name": "isActivityEnabled", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"is_activity_enabled\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"is_activity_enabled\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('1')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "order", + "getter_name": "order", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(AlbumAssetOrder.values)", + "dart_type_name": "AlbumAssetOrder" + } + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 5, + "references": [ + 4 + ], + "type": "table", + "data": { + "name": "local_album_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "name", + "getter_name": "name", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "updated_at", + "getter_name": "updatedAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "backup_selection", + "getter_name": "backupSelection", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(BackupSelection.values)", + "dart_type_name": "BackupSelection" + } + }, + { + "name": "is_ios_shared_album", + "getter_name": "isIosSharedAlbum", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"is_ios_shared_album\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"is_ios_shared_album\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "linked_remote_album_id", + "getter_name": "linkedRemoteAlbumId", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "defaultConstraints": "REFERENCES remote_album_entity (id) ON DELETE SET NULL", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES remote_album_entity (id) ON DELETE SET NULL" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "remote_album_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "setNull" + } + } + ] + }, + { + "name": "marker", + "getter_name": "marker_", + "moor_type": "bool", + "nullable": true, + "customConstraints": null, + "defaultConstraints": "CHECK (\"marker\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"marker\" IN (0, 1))" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 6, + "references": [ + 3, + 5 + ], + "type": "table", + "data": { + "name": "local_album_asset_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "asset_id", + "getter_name": "assetId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES local_asset_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES local_asset_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "local_asset_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "album_id", + "getter_name": "albumId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES local_album_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES local_album_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "local_album_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "marker", + "getter_name": "marker_", + "moor_type": "bool", + "nullable": true, + "customConstraints": null, + "defaultConstraints": "CHECK (\"marker\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"marker\" IN (0, 1))" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "asset_id", + "album_id" + ] + } + }, + { + "id": 7, + "references": [ + 6 + ], + "type": "index", + "data": { + "on": 6, + "name": "idx_local_album_asset_album_asset", + "sql": "CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 8, + "references": [ + 3 + ], + "type": "index", + "data": { + "on": 3, + "name": "idx_local_asset_checksum", + "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)", + "unique": false, + "columns": [] + } + }, + { + "id": 9, + "references": [ + 3 + ], + "type": "index", + "data": { + "on": 3, + "name": "idx_local_asset_cloud_id", + "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 10, + "references": [ + 2 + ], + "type": "index", + "data": { + "on": 2, + "name": "idx_stack_primary_asset_id", + "sql": "CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 11, + "references": [ + 1 + ], + "type": "index", + "data": { + "on": 1, + "name": "UQ_remote_assets_owner_checksum", + "sql": "CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum\nON remote_asset_entity (owner_id, checksum)\nWHERE (library_id IS NULL);\n", + "unique": true, + "columns": [] + } + }, + { + "id": 12, + "references": [ + 1 + ], + "type": "index", + "data": { + "on": 1, + "name": "UQ_remote_assets_owner_library_checksum", + "sql": "CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum\nON remote_asset_entity (owner_id, library_id, checksum)\nWHERE (library_id IS NOT NULL);\n", + "unique": true, + "columns": [] + } + }, + { + "id": 13, + "references": [ + 1 + ], + "type": "index", + "data": { + "on": 1, + "name": "idx_remote_asset_checksum", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)", + "unique": false, + "columns": [] + } + }, + { + "id": 14, + "references": [ + 1 + ], + "type": "index", + "data": { + "on": 1, + "name": "idx_remote_asset_stack_id", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 15, + "references": [ + 1 + ], + "type": "index", + "data": { + "on": 1, + "name": "idx_remote_asset_owner_visibility_deleted_created", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_visibility_deleted_created\nON remote_asset_entity (owner_id, visibility, deleted_at, created_at DESC)\n", + "unique": false, + "columns": [] + } + }, + { + "id": 16, + "references": [], + "type": "table", + "data": { + "name": "auth_user_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "name", + "getter_name": "name", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "email", + "getter_name": "email", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "is_admin", + "getter_name": "isAdmin", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"is_admin\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"is_admin\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "has_profile_image", + "getter_name": "hasProfileImage", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"has_profile_image\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"has_profile_image\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "profile_changed_at", + "getter_name": "profileChangedAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "avatar_color", + "getter_name": "avatarColor", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(AvatarColor.values)", + "dart_type_name": "AvatarColor" + } + }, + { + "name": "quota_size_in_bytes", + "getter_name": "quotaSizeInBytes", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "quota_usage_in_bytes", + "getter_name": "quotaUsageInBytes", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "pin_code", + "getter_name": "pinCode", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 17, + "references": [ + 0 + ], + "type": "table", + "data": { + "name": "user_metadata_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "user_id", + "getter_name": "userId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "user_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "key", + "getter_name": "key", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(UserMetadataKey.values)", + "dart_type_name": "UserMetadataKey" + } + }, + { + "name": "value", + "getter_name": "value", + "moor_type": "blob", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "userMetadataConverter", + "dart_type_name": "Map" + } + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "user_id", + "key" + ] + } + }, + { + "id": 18, + "references": [ + 0 + ], + "type": "table", + "data": { + "name": "partner_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "shared_by_id", + "getter_name": "sharedById", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "user_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "shared_with_id", + "getter_name": "sharedWithId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "user_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "in_timeline", + "getter_name": "inTimeline", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"in_timeline\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"in_timeline\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "shared_by_id", + "shared_with_id" + ] + } + }, + { + "id": 19, + "references": [ + 1 + ], + "type": "table", + "data": { + "name": "remote_exif_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "asset_id", + "getter_name": "assetId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "remote_asset_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "city", + "getter_name": "city", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "state", + "getter_name": "state", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "country", + "getter_name": "country", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "date_time_original", + "getter_name": "dateTimeOriginal", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "description", + "getter_name": "description", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "height", + "getter_name": "height", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "width", + "getter_name": "width", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "exposure_time", + "getter_name": "exposureTime", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "f_number", + "getter_name": "fNumber", + "moor_type": "double", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "file_size", + "getter_name": "fileSize", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "focal_length", + "getter_name": "focalLength", + "moor_type": "double", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "latitude", + "getter_name": "latitude", + "moor_type": "double", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "longitude", + "getter_name": "longitude", + "moor_type": "double", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "iso", + "getter_name": "iso", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "make", + "getter_name": "make", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "model", + "getter_name": "model", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "lens", + "getter_name": "lens", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "orientation", + "getter_name": "orientation", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "time_zone", + "getter_name": "timeZone", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "rating", + "getter_name": "rating", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "projection_type", + "getter_name": "projectionType", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "asset_id" + ] + } + }, + { + "id": 20, + "references": [ + 1, + 4 + ], + "type": "table", + "data": { + "name": "remote_album_asset_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "asset_id", + "getter_name": "assetId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "remote_asset_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "album_id", + "getter_name": "albumId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES remote_album_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES remote_album_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "remote_album_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "asset_id", + "album_id" + ] + } + }, + { + "id": 21, + "references": [ + 4, + 0 + ], + "type": "table", + "data": { + "name": "remote_album_user_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "album_id", + "getter_name": "albumId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES remote_album_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES remote_album_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "remote_album_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "user_id", + "getter_name": "userId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "user_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "role", + "getter_name": "role", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(AlbumUserRole.values)", + "dart_type_name": "AlbumUserRole" + } + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "album_id", + "user_id" + ] + } + }, + { + "id": 22, + "references": [ + 1 + ], + "type": "table", + "data": { + "name": "remote_asset_cloud_id_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "asset_id", + "getter_name": "assetId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "remote_asset_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "cloud_id", + "getter_name": "cloudId", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "created_at", + "getter_name": "createdAt", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "adjustment_time", + "getter_name": "adjustmentTime", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "latitude", + "getter_name": "latitude", + "moor_type": "double", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "longitude", + "getter_name": "longitude", + "moor_type": "double", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "asset_id" + ] + } + }, + { + "id": 23, + "references": [ + 0 + ], + "type": "table", + "data": { + "name": "memory_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "created_at", + "getter_name": "createdAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "updated_at", + "getter_name": "updatedAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "deleted_at", + "getter_name": "deletedAt", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "owner_id", + "getter_name": "ownerId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "user_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "type", + "getter_name": "type", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(MemoryTypeEnum.values)", + "dart_type_name": "MemoryTypeEnum" + } + }, + { + "name": "data", + "getter_name": "data", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "is_saved", + "getter_name": "isSaved", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"is_saved\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"is_saved\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "memory_at", + "getter_name": "memoryAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "seen_at", + "getter_name": "seenAt", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "show_at", + "getter_name": "showAt", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "hide_at", + "getter_name": "hideAt", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 24, + "references": [ + 1, + 23 + ], + "type": "table", + "data": { + "name": "memory_asset_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "asset_id", + "getter_name": "assetId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "remote_asset_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "memory_id", + "getter_name": "memoryId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES memory_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES memory_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "memory_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "asset_id", + "memory_id" + ] + } + }, + { + "id": 25, + "references": [ + 0 + ], + "type": "table", + "data": { + "name": "person_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "created_at", + "getter_name": "createdAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "updated_at", + "getter_name": "updatedAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "owner_id", + "getter_name": "ownerId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "user_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "name", + "getter_name": "name", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "face_asset_id", + "getter_name": "faceAssetId", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "is_favorite", + "getter_name": "isFavorite", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"is_favorite\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"is_favorite\" IN (0, 1))" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "is_hidden", + "getter_name": "isHidden", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"is_hidden\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"is_hidden\" IN (0, 1))" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "color", + "getter_name": "color", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "birth_date", + "getter_name": "birthDate", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 26, + "references": [ + 1, + 25 + ], + "type": "table", + "data": { + "name": "asset_face_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "asset_id", + "getter_name": "assetId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "remote_asset_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "person_id", + "getter_name": "personId", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "defaultConstraints": "REFERENCES person_entity (id) ON DELETE SET NULL", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES person_entity (id) ON DELETE SET NULL" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "person_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "setNull" + } + } + ] + }, + { + "name": "image_width", + "getter_name": "imageWidth", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "image_height", + "getter_name": "imageHeight", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "bounding_box_x1", + "getter_name": "boundingBoxX1", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "bounding_box_y1", + "getter_name": "boundingBoxY1", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "bounding_box_x2", + "getter_name": "boundingBoxX2", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "bounding_box_y2", + "getter_name": "boundingBoxY2", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "source_type", + "getter_name": "sourceType", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "is_visible", + "getter_name": "isVisible", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"is_visible\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"is_visible\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('1')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "deleted_at", + "getter_name": "deletedAt", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 27, + "references": [], + "type": "table", + "data": { + "name": "store_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "string_value", + "getter_name": "stringValue", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "int_value", + "getter_name": "intValue", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 28, + "references": [], + "type": "table", + "data": { + "name": "trashed_local_asset_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "name", + "getter_name": "name", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "type", + "getter_name": "type", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(AssetType.values)", + "dart_type_name": "AssetType" + } + }, + { + "name": "created_at", + "getter_name": "createdAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "updated_at", + "getter_name": "updatedAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "width", + "getter_name": "width", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "height", + "getter_name": "height", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "duration_ms", + "getter_name": "durationMs", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "album_id", + "getter_name": "albumId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "checksum", + "getter_name": "checksum", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "is_favorite", + "getter_name": "isFavorite", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"is_favorite\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"is_favorite\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "orientation", + "getter_name": "orientation", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "source", + "getter_name": "source", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(TrashOrigin.values)", + "dart_type_name": "TrashOrigin" + } + }, + { + "name": "playback_style", + "getter_name": "playbackStyle", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(AssetPlaybackStyle.values)", + "dart_type_name": "AssetPlaybackStyle" + } + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id", + "album_id" + ] + } + }, + { + "id": 29, + "references": [ + 1 + ], + "type": "table", + "data": { + "name": "asset_edit_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "asset_id", + "getter_name": "assetId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "remote_asset_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "action", + "getter_name": "action", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(AssetEditAction.values)", + "dart_type_name": "AssetEditAction" + } + }, + { + "name": "parameters", + "getter_name": "parameters", + "moor_type": "blob", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "editParameterConverter", + "dart_type_name": "Map" + } + }, + { + "name": "sequence", + "getter_name": "sequence", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 30, + "references": [], + "type": "table", + "data": { + "name": "settings", + "was_declared_in_moor": false, + "columns": [ + { + "name": "key", + "getter_name": "key", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "value", + "getter_name": "value", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "updated_at", + "getter_name": "updatedAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "key" + ] + } + }, + { + "id": 31, + "references": [ + 18 + ], + "type": "index", + "data": { + "on": 18, + "name": "idx_partner_shared_with_id", + "sql": "CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 32, + "references": [ + 19 + ], + "type": "index", + "data": { + "on": 19, + "name": "idx_lat_lng", + "sql": "CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)", + "unique": false, + "columns": [] + } + }, + { + "id": 33, + "references": [ + 19 + ], + "type": "index", + "data": { + "on": 19, + "name": "idx_remote_exif_city", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_exif_city\nON remote_exif_entity (city) WHERE city IS NOT NULL\n", + "unique": false, + "columns": [] + } + }, + { + "id": 34, + "references": [ + 20 + ], + "type": "index", + "data": { + "on": 20, + "name": "idx_remote_album_asset_album_asset", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 35, + "references": [ + 22 + ], + "type": "index", + "data": { + "on": 22, + "name": "idx_remote_asset_cloud_id", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 36, + "references": [ + 25 + ], + "type": "index", + "data": { + "on": 25, + "name": "idx_person_owner_id", + "sql": "CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 37, + "references": [ + 26 + ], + "type": "index", + "data": { + "on": 26, + "name": "idx_asset_face_person_id", + "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 38, + "references": [ + 26 + ], + "type": "index", + "data": { + "on": 26, + "name": "idx_asset_face_asset_id", + "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 39, + "references": [ + 26 + ], + "type": "index", + "data": { + "on": 26, + "name": "idx_asset_face_visible_person", + "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_visible_person\nON asset_face_entity (person_id, asset_id)\nWHERE is_visible = 1 AND deleted_at IS NULL\n", + "unique": false, + "columns": [] + } + }, + { + "id": 40, + "references": [ + 28 + ], + "type": "index", + "data": { + "on": 28, + "name": "idx_trashed_local_asset_checksum", + "sql": "CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)", + "unique": false, + "columns": [] + } + }, + { + "id": 41, + "references": [ + 28 + ], + "type": "index", + "data": { + "on": 28, + "name": "idx_trashed_local_asset_album", + "sql": "CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 42, + "references": [ + 29 + ], + "type": "index", + "data": { + "on": 29, + "name": "idx_asset_edit_asset_id", + "sql": "CREATE INDEX IF NOT EXISTS idx_asset_edit_asset_id ON asset_edit_entity (asset_id)", + "unique": false, + "columns": [] + } + } + ], + "fixed_sql": [ + { + "name": "user_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"user_entity\" (\"id\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"email\" TEXT NOT NULL, \"has_profile_image\" INTEGER NOT NULL DEFAULT 0 CHECK (\"has_profile_image\" IN (0, 1)), \"profile_changed_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"avatar_color\" INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "remote_asset_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"remote_asset_entity\" (\"name\" TEXT NOT NULL, \"type\" INTEGER NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"width\" INTEGER NULL, \"height\" INTEGER NULL, \"duration_ms\" INTEGER NULL, \"id\" TEXT NOT NULL, \"checksum\" TEXT NOT NULL, \"is_favorite\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_favorite\" IN (0, 1)), \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"local_date_time\" TEXT NULL, \"thumb_hash\" TEXT NULL, \"deleted_at\" TEXT NULL, \"uploaded_at\" TEXT NULL, \"live_photo_video_id\" TEXT NULL, \"visibility\" INTEGER NOT NULL, \"stack_id\" TEXT NULL, \"library_id\" TEXT NULL, \"is_edited\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_edited\" IN (0, 1)), PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "stack_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"stack_entity\" (\"id\" TEXT NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"primary_asset_id\" TEXT NOT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "local_asset_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"local_asset_entity\" (\"name\" TEXT NOT NULL, \"type\" INTEGER NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"width\" INTEGER NULL, \"height\" INTEGER NULL, \"duration_ms\" INTEGER NULL, \"id\" TEXT NOT NULL, \"checksum\" TEXT NULL, \"is_favorite\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_favorite\" IN (0, 1)), \"orientation\" INTEGER NOT NULL DEFAULT 0, \"i_cloud_id\" TEXT NULL, \"adjustment_time\" TEXT NULL, \"latitude\" REAL NULL, \"longitude\" REAL NULL, \"playback_style\" INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "remote_album_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"remote_album_entity\" (\"id\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"description\" TEXT NOT NULL DEFAULT '', \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"thumbnail_asset_id\" TEXT NULL REFERENCES remote_asset_entity (id) ON DELETE SET NULL, \"is_activity_enabled\" INTEGER NOT NULL DEFAULT 1 CHECK (\"is_activity_enabled\" IN (0, 1)), \"order\" INTEGER NOT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "local_album_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"local_album_entity\" (\"id\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"backup_selection\" INTEGER NOT NULL, \"is_ios_shared_album\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_ios_shared_album\" IN (0, 1)), \"linked_remote_album_id\" TEXT NULL REFERENCES remote_album_entity (id) ON DELETE SET NULL, \"marker\" INTEGER NULL CHECK (\"marker\" IN (0, 1)), PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "local_album_asset_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"local_album_asset_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES local_asset_entity (id) ON DELETE CASCADE, \"album_id\" TEXT NOT NULL REFERENCES local_album_entity (id) ON DELETE CASCADE, \"marker\" INTEGER NULL CHECK (\"marker\" IN (0, 1)), PRIMARY KEY (\"asset_id\", \"album_id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "idx_local_album_asset_album_asset", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)" + } + ] + }, + { + "name": "idx_local_asset_checksum", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)" + } + ] + }, + { + "name": "idx_local_asset_cloud_id", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)" + } + ] + }, + { + "name": "idx_stack_primary_asset_id", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)" + } + ] + }, + { + "name": "UQ_remote_assets_owner_checksum", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)" + } + ] + }, + { + "name": "UQ_remote_assets_owner_library_checksum", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)" + } + ] + }, + { + "name": "idx_remote_asset_checksum", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)" + } + ] + }, + { + "name": "idx_remote_asset_stack_id", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)" + } + ] + }, + { + "name": "idx_remote_asset_owner_visibility_deleted_created", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_visibility_deleted_created ON remote_asset_entity (owner_id, visibility, deleted_at, created_at DESC)" + } + ] + }, + { + "name": "auth_user_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"auth_user_entity\" (\"id\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"email\" TEXT NOT NULL, \"is_admin\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_admin\" IN (0, 1)), \"has_profile_image\" INTEGER NOT NULL DEFAULT 0 CHECK (\"has_profile_image\" IN (0, 1)), \"profile_changed_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"avatar_color\" INTEGER NOT NULL, \"quota_size_in_bytes\" INTEGER NOT NULL DEFAULT 0, \"quota_usage_in_bytes\" INTEGER NOT NULL DEFAULT 0, \"pin_code\" TEXT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "user_metadata_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"user_metadata_entity\" (\"user_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"key\" INTEGER NOT NULL, \"value\" BLOB NOT NULL, PRIMARY KEY (\"user_id\", \"key\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "partner_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"partner_entity\" (\"shared_by_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"shared_with_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"in_timeline\" INTEGER NOT NULL DEFAULT 0 CHECK (\"in_timeline\" IN (0, 1)), PRIMARY KEY (\"shared_by_id\", \"shared_with_id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "remote_exif_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"remote_exif_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"city\" TEXT NULL, \"state\" TEXT NULL, \"country\" TEXT NULL, \"date_time_original\" TEXT NULL, \"description\" TEXT NULL, \"height\" INTEGER NULL, \"width\" INTEGER NULL, \"exposure_time\" TEXT NULL, \"f_number\" REAL NULL, \"file_size\" INTEGER NULL, \"focal_length\" REAL NULL, \"latitude\" REAL NULL, \"longitude\" REAL NULL, \"iso\" INTEGER NULL, \"make\" TEXT NULL, \"model\" TEXT NULL, \"lens\" TEXT NULL, \"orientation\" TEXT NULL, \"time_zone\" TEXT NULL, \"rating\" INTEGER NULL, \"projection_type\" TEXT NULL, PRIMARY KEY (\"asset_id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "remote_album_asset_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"remote_album_asset_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"album_id\" TEXT NOT NULL REFERENCES remote_album_entity (id) ON DELETE CASCADE, PRIMARY KEY (\"asset_id\", \"album_id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "remote_album_user_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"remote_album_user_entity\" (\"album_id\" TEXT NOT NULL REFERENCES remote_album_entity (id) ON DELETE CASCADE, \"user_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"role\" INTEGER NOT NULL, PRIMARY KEY (\"album_id\", \"user_id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "remote_asset_cloud_id_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"remote_asset_cloud_id_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"cloud_id\" TEXT NULL, \"created_at\" TEXT NULL, \"adjustment_time\" TEXT NULL, \"latitude\" REAL NULL, \"longitude\" REAL NULL, PRIMARY KEY (\"asset_id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "memory_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"memory_entity\" (\"id\" TEXT NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"deleted_at\" TEXT NULL, \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"type\" INTEGER NOT NULL, \"data\" TEXT NOT NULL, \"is_saved\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_saved\" IN (0, 1)), \"memory_at\" TEXT NOT NULL, \"seen_at\" TEXT NULL, \"show_at\" TEXT NULL, \"hide_at\" TEXT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "memory_asset_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"memory_asset_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"memory_id\" TEXT NOT NULL REFERENCES memory_entity (id) ON DELETE CASCADE, PRIMARY KEY (\"asset_id\", \"memory_id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "person_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"person_entity\" (\"id\" TEXT NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"name\" TEXT NOT NULL, \"face_asset_id\" TEXT NULL, \"is_favorite\" INTEGER NOT NULL CHECK (\"is_favorite\" IN (0, 1)), \"is_hidden\" INTEGER NOT NULL CHECK (\"is_hidden\" IN (0, 1)), \"color\" TEXT NULL, \"birth_date\" TEXT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "asset_face_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"asset_face_entity\" (\"id\" TEXT NOT NULL, \"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"person_id\" TEXT NULL REFERENCES person_entity (id) ON DELETE SET NULL, \"image_width\" INTEGER NOT NULL, \"image_height\" INTEGER NOT NULL, \"bounding_box_x1\" INTEGER NOT NULL, \"bounding_box_y1\" INTEGER NOT NULL, \"bounding_box_x2\" INTEGER NOT NULL, \"bounding_box_y2\" INTEGER NOT NULL, \"source_type\" TEXT NOT NULL, \"is_visible\" INTEGER NOT NULL DEFAULT 1 CHECK (\"is_visible\" IN (0, 1)), \"deleted_at\" TEXT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "store_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"store_entity\" (\"id\" INTEGER NOT NULL, \"string_value\" TEXT NULL, \"int_value\" INTEGER NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "trashed_local_asset_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"trashed_local_asset_entity\" (\"name\" TEXT NOT NULL, \"type\" INTEGER NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"width\" INTEGER NULL, \"height\" INTEGER NULL, \"duration_ms\" INTEGER NULL, \"id\" TEXT NOT NULL, \"album_id\" TEXT NOT NULL, \"checksum\" TEXT NULL, \"is_favorite\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_favorite\" IN (0, 1)), \"orientation\" INTEGER NOT NULL DEFAULT 0, \"source\" INTEGER NOT NULL, \"playback_style\" INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (\"id\", \"album_id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "asset_edit_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"asset_edit_entity\" (\"id\" TEXT NOT NULL, \"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"action\" INTEGER NOT NULL, \"parameters\" BLOB NOT NULL, \"sequence\" INTEGER NOT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "settings", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"settings\" (\"key\" TEXT NOT NULL, \"value\" TEXT NOT NULL, \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), PRIMARY KEY (\"key\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "idx_partner_shared_with_id", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)" + } + ] + }, + { + "name": "idx_lat_lng", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)" + } + ] + }, + { + "name": "idx_remote_exif_city", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_exif_city ON remote_exif_entity (city) WHERE city IS NOT NULL" + } + ] + }, + { + "name": "idx_remote_album_asset_album_asset", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)" + } + ] + }, + { + "name": "idx_remote_asset_cloud_id", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)" + } + ] + }, + { + "name": "idx_person_owner_id", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)" + } + ] + }, + { + "name": "idx_asset_face_person_id", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)" + } + ] + }, + { + "name": "idx_asset_face_asset_id", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)" + } + ] + }, + { + "name": "idx_asset_face_visible_person", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_visible_person ON asset_face_entity (person_id, asset_id) WHERE is_visible = 1 AND deleted_at IS NULL" + } + ] + }, + { + "name": "idx_trashed_local_asset_checksum", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)" + } + ] + }, + { + "name": "idx_trashed_local_asset_album", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)" + } + ] + }, + { + "name": "idx_asset_edit_asset_id", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_asset_edit_asset_id ON asset_edit_entity (asset_id)" + } + ] + } + ] +} \ No newline at end of file diff --git a/mobile/drift_schemas/main/drift_schema_v28.json b/mobile/drift_schemas/main/drift_schema_v28.json new file mode 100644 index 0000000000..c635d0a6a2 --- /dev/null +++ b/mobile/drift_schemas/main/drift_schema_v28.json @@ -0,0 +1,3391 @@ +{ + "_meta": { + "description": "This file contains a serialized version of schema entities for drift.", + "version": "1.3.0" + }, + "options": { + "store_date_time_values_as_text": true + }, + "entities": [ + { + "id": 0, + "references": [], + "type": "table", + "data": { + "name": "user_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "name", + "getter_name": "name", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "email", + "getter_name": "email", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "has_profile_image", + "getter_name": "hasProfileImage", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"has_profile_image\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"has_profile_image\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "profile_changed_at", + "getter_name": "profileChangedAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "avatar_color", + "getter_name": "avatarColor", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(AvatarColor.values)", + "dart_type_name": "AvatarColor" + } + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 1, + "references": [ + 0 + ], + "type": "table", + "data": { + "name": "remote_asset_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "name", + "getter_name": "name", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "type", + "getter_name": "type", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(AssetType.values)", + "dart_type_name": "AssetType" + } + }, + { + "name": "created_at", + "getter_name": "createdAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "updated_at", + "getter_name": "updatedAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "width", + "getter_name": "width", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "height", + "getter_name": "height", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "duration_ms", + "getter_name": "durationMs", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "checksum", + "getter_name": "checksum", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "is_favorite", + "getter_name": "isFavorite", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"is_favorite\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"is_favorite\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "owner_id", + "getter_name": "ownerId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "user_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "local_date_time", + "getter_name": "localDateTime", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "thumb_hash", + "getter_name": "thumbHash", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "deleted_at", + "getter_name": "deletedAt", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "uploaded_at", + "getter_name": "uploadedAt", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "live_photo_video_id", + "getter_name": "livePhotoVideoId", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "visibility", + "getter_name": "visibility", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(AssetVisibility.values)", + "dart_type_name": "AssetVisibility" + } + }, + { + "name": "stack_id", + "getter_name": "stackId", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "library_id", + "getter_name": "libraryId", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "is_edited", + "getter_name": "isEdited", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"is_edited\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"is_edited\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 2, + "references": [ + 0 + ], + "type": "table", + "data": { + "name": "stack_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "created_at", + "getter_name": "createdAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "updated_at", + "getter_name": "updatedAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "owner_id", + "getter_name": "ownerId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "user_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "primary_asset_id", + "getter_name": "primaryAssetId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 3, + "references": [], + "type": "table", + "data": { + "name": "local_asset_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "name", + "getter_name": "name", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "type", + "getter_name": "type", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(AssetType.values)", + "dart_type_name": "AssetType" + } + }, + { + "name": "created_at", + "getter_name": "createdAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "updated_at", + "getter_name": "updatedAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "width", + "getter_name": "width", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "height", + "getter_name": "height", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "duration_ms", + "getter_name": "durationMs", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "checksum", + "getter_name": "checksum", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "is_favorite", + "getter_name": "isFavorite", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"is_favorite\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"is_favorite\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "orientation", + "getter_name": "orientation", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "i_cloud_id", + "getter_name": "iCloudId", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "adjustment_time", + "getter_name": "adjustmentTime", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "latitude", + "getter_name": "latitude", + "moor_type": "double", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "longitude", + "getter_name": "longitude", + "moor_type": "double", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "playback_style", + "getter_name": "playbackStyle", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(AssetPlaybackStyle.values)", + "dart_type_name": "AssetPlaybackStyle" + } + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 4, + "references": [ + 1 + ], + "type": "table", + "data": { + "name": "remote_album_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "name", + "getter_name": "name", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "description", + "getter_name": "description", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('\\'\\'')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "created_at", + "getter_name": "createdAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "updated_at", + "getter_name": "updatedAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "thumbnail_asset_id", + "getter_name": "thumbnailAssetId", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE SET NULL", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE SET NULL" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "remote_asset_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "setNull" + } + } + ] + }, + { + "name": "is_activity_enabled", + "getter_name": "isActivityEnabled", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"is_activity_enabled\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"is_activity_enabled\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('1')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "order", + "getter_name": "order", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(AlbumAssetOrder.values)", + "dart_type_name": "AlbumAssetOrder" + } + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 5, + "references": [ + 4 + ], + "type": "table", + "data": { + "name": "local_album_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "name", + "getter_name": "name", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "updated_at", + "getter_name": "updatedAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "backup_selection", + "getter_name": "backupSelection", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(BackupSelection.values)", + "dart_type_name": "BackupSelection" + } + }, + { + "name": "is_ios_shared_album", + "getter_name": "isIosSharedAlbum", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"is_ios_shared_album\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"is_ios_shared_album\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "linked_remote_album_id", + "getter_name": "linkedRemoteAlbumId", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "defaultConstraints": "REFERENCES remote_album_entity (id) ON DELETE SET NULL", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES remote_album_entity (id) ON DELETE SET NULL" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "remote_album_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "setNull" + } + } + ] + }, + { + "name": "marker", + "getter_name": "marker_", + "moor_type": "bool", + "nullable": true, + "customConstraints": null, + "defaultConstraints": "CHECK (\"marker\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"marker\" IN (0, 1))" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 6, + "references": [ + 3, + 5 + ], + "type": "table", + "data": { + "name": "local_album_asset_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "asset_id", + "getter_name": "assetId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES local_asset_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES local_asset_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "local_asset_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "album_id", + "getter_name": "albumId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES local_album_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES local_album_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "local_album_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "marker", + "getter_name": "marker_", + "moor_type": "bool", + "nullable": true, + "customConstraints": null, + "defaultConstraints": "CHECK (\"marker\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"marker\" IN (0, 1))" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "asset_id", + "album_id" + ] + } + }, + { + "id": 7, + "references": [ + 6 + ], + "type": "index", + "data": { + "on": 6, + "name": "idx_local_album_asset_album_asset", + "sql": "CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 8, + "references": [ + 3 + ], + "type": "index", + "data": { + "on": 3, + "name": "idx_local_asset_checksum", + "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)", + "unique": false, + "columns": [] + } + }, + { + "id": 9, + "references": [ + 3 + ], + "type": "index", + "data": { + "on": 3, + "name": "idx_local_asset_cloud_id", + "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 10, + "references": [ + 3 + ], + "type": "index", + "data": { + "on": 3, + "name": "idx_local_asset_created_at", + "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_created_at ON local_asset_entity (created_at)", + "unique": false, + "columns": [] + } + }, + { + "id": 11, + "references": [ + 2 + ], + "type": "index", + "data": { + "on": 2, + "name": "idx_stack_primary_asset_id", + "sql": "CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 12, + "references": [ + 1 + ], + "type": "index", + "data": { + "on": 1, + "name": "UQ_remote_assets_owner_checksum", + "sql": "CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum\nON remote_asset_entity (owner_id, checksum)\nWHERE (library_id IS NULL);\n", + "unique": true, + "columns": [] + } + }, + { + "id": 13, + "references": [ + 1 + ], + "type": "index", + "data": { + "on": 1, + "name": "UQ_remote_assets_owner_library_checksum", + "sql": "CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum\nON remote_asset_entity (owner_id, library_id, checksum)\nWHERE (library_id IS NOT NULL);\n", + "unique": true, + "columns": [] + } + }, + { + "id": 14, + "references": [ + 1 + ], + "type": "index", + "data": { + "on": 1, + "name": "idx_remote_asset_checksum", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)", + "unique": false, + "columns": [] + } + }, + { + "id": 15, + "references": [ + 1 + ], + "type": "index", + "data": { + "on": 1, + "name": "idx_remote_asset_stack_id", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 16, + "references": [ + 1 + ], + "type": "index", + "data": { + "on": 1, + "name": "idx_remote_asset_owner_visibility_deleted_created", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_visibility_deleted_created\nON remote_asset_entity (owner_id, visibility, deleted_at, created_at DESC)\n", + "unique": false, + "columns": [] + } + }, + { + "id": 17, + "references": [], + "type": "table", + "data": { + "name": "auth_user_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "name", + "getter_name": "name", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "email", + "getter_name": "email", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "is_admin", + "getter_name": "isAdmin", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"is_admin\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"is_admin\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "has_profile_image", + "getter_name": "hasProfileImage", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"has_profile_image\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"has_profile_image\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "profile_changed_at", + "getter_name": "profileChangedAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "avatar_color", + "getter_name": "avatarColor", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(AvatarColor.values)", + "dart_type_name": "AvatarColor" + } + }, + { + "name": "quota_size_in_bytes", + "getter_name": "quotaSizeInBytes", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "quota_usage_in_bytes", + "getter_name": "quotaUsageInBytes", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "pin_code", + "getter_name": "pinCode", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 18, + "references": [ + 0 + ], + "type": "table", + "data": { + "name": "user_metadata_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "user_id", + "getter_name": "userId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "user_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "key", + "getter_name": "key", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(UserMetadataKey.values)", + "dart_type_name": "UserMetadataKey" + } + }, + { + "name": "value", + "getter_name": "value", + "moor_type": "blob", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "userMetadataConverter", + "dart_type_name": "Map" + } + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "user_id", + "key" + ] + } + }, + { + "id": 19, + "references": [ + 0 + ], + "type": "table", + "data": { + "name": "partner_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "shared_by_id", + "getter_name": "sharedById", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "user_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "shared_with_id", + "getter_name": "sharedWithId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "user_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "in_timeline", + "getter_name": "inTimeline", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"in_timeline\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"in_timeline\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "shared_by_id", + "shared_with_id" + ] + } + }, + { + "id": 20, + "references": [ + 1 + ], + "type": "table", + "data": { + "name": "remote_exif_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "asset_id", + "getter_name": "assetId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "remote_asset_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "city", + "getter_name": "city", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "state", + "getter_name": "state", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "country", + "getter_name": "country", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "date_time_original", + "getter_name": "dateTimeOriginal", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "description", + "getter_name": "description", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "height", + "getter_name": "height", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "width", + "getter_name": "width", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "exposure_time", + "getter_name": "exposureTime", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "f_number", + "getter_name": "fNumber", + "moor_type": "double", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "file_size", + "getter_name": "fileSize", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "focal_length", + "getter_name": "focalLength", + "moor_type": "double", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "latitude", + "getter_name": "latitude", + "moor_type": "double", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "longitude", + "getter_name": "longitude", + "moor_type": "double", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "iso", + "getter_name": "iso", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "make", + "getter_name": "make", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "model", + "getter_name": "model", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "lens", + "getter_name": "lens", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "orientation", + "getter_name": "orientation", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "time_zone", + "getter_name": "timeZone", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "rating", + "getter_name": "rating", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "projection_type", + "getter_name": "projectionType", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "asset_id" + ] + } + }, + { + "id": 21, + "references": [ + 1, + 4 + ], + "type": "table", + "data": { + "name": "remote_album_asset_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "asset_id", + "getter_name": "assetId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "remote_asset_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "album_id", + "getter_name": "albumId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES remote_album_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES remote_album_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "remote_album_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "asset_id", + "album_id" + ] + } + }, + { + "id": 22, + "references": [ + 4, + 0 + ], + "type": "table", + "data": { + "name": "remote_album_user_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "album_id", + "getter_name": "albumId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES remote_album_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES remote_album_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "remote_album_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "user_id", + "getter_name": "userId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "user_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "role", + "getter_name": "role", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(AlbumUserRole.values)", + "dart_type_name": "AlbumUserRole" + } + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "album_id", + "user_id" + ] + } + }, + { + "id": 23, + "references": [ + 1 + ], + "type": "table", + "data": { + "name": "remote_asset_cloud_id_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "asset_id", + "getter_name": "assetId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "remote_asset_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "cloud_id", + "getter_name": "cloudId", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "created_at", + "getter_name": "createdAt", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "adjustment_time", + "getter_name": "adjustmentTime", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "latitude", + "getter_name": "latitude", + "moor_type": "double", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "longitude", + "getter_name": "longitude", + "moor_type": "double", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "asset_id" + ] + } + }, + { + "id": 24, + "references": [ + 0 + ], + "type": "table", + "data": { + "name": "memory_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "created_at", + "getter_name": "createdAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "updated_at", + "getter_name": "updatedAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "deleted_at", + "getter_name": "deletedAt", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "owner_id", + "getter_name": "ownerId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "user_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "type", + "getter_name": "type", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(MemoryTypeEnum.values)", + "dart_type_name": "MemoryTypeEnum" + } + }, + { + "name": "data", + "getter_name": "data", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "is_saved", + "getter_name": "isSaved", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"is_saved\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"is_saved\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "memory_at", + "getter_name": "memoryAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "seen_at", + "getter_name": "seenAt", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "show_at", + "getter_name": "showAt", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "hide_at", + "getter_name": "hideAt", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 25, + "references": [ + 1, + 24 + ], + "type": "table", + "data": { + "name": "memory_asset_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "asset_id", + "getter_name": "assetId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "remote_asset_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "memory_id", + "getter_name": "memoryId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES memory_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES memory_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "memory_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "asset_id", + "memory_id" + ] + } + }, + { + "id": 26, + "references": [ + 0 + ], + "type": "table", + "data": { + "name": "person_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "created_at", + "getter_name": "createdAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "updated_at", + "getter_name": "updatedAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "owner_id", + "getter_name": "ownerId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "user_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "name", + "getter_name": "name", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "face_asset_id", + "getter_name": "faceAssetId", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "is_favorite", + "getter_name": "isFavorite", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"is_favorite\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"is_favorite\" IN (0, 1))" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "is_hidden", + "getter_name": "isHidden", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"is_hidden\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"is_hidden\" IN (0, 1))" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "color", + "getter_name": "color", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "birth_date", + "getter_name": "birthDate", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 27, + "references": [ + 1, + 26 + ], + "type": "table", + "data": { + "name": "asset_face_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "asset_id", + "getter_name": "assetId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "remote_asset_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "person_id", + "getter_name": "personId", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "defaultConstraints": "REFERENCES person_entity (id) ON DELETE SET NULL", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES person_entity (id) ON DELETE SET NULL" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "person_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "setNull" + } + } + ] + }, + { + "name": "image_width", + "getter_name": "imageWidth", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "image_height", + "getter_name": "imageHeight", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "bounding_box_x1", + "getter_name": "boundingBoxX1", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "bounding_box_y1", + "getter_name": "boundingBoxY1", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "bounding_box_x2", + "getter_name": "boundingBoxX2", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "bounding_box_y2", + "getter_name": "boundingBoxY2", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "source_type", + "getter_name": "sourceType", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "is_visible", + "getter_name": "isVisible", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"is_visible\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"is_visible\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('1')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "deleted_at", + "getter_name": "deletedAt", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 28, + "references": [], + "type": "table", + "data": { + "name": "store_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "string_value", + "getter_name": "stringValue", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "int_value", + "getter_name": "intValue", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 29, + "references": [], + "type": "table", + "data": { + "name": "trashed_local_asset_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "name", + "getter_name": "name", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "type", + "getter_name": "type", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(AssetType.values)", + "dart_type_name": "AssetType" + } + }, + { + "name": "created_at", + "getter_name": "createdAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "updated_at", + "getter_name": "updatedAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "width", + "getter_name": "width", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "height", + "getter_name": "height", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "duration_ms", + "getter_name": "durationMs", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "album_id", + "getter_name": "albumId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "checksum", + "getter_name": "checksum", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "is_favorite", + "getter_name": "isFavorite", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"is_favorite\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"is_favorite\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "orientation", + "getter_name": "orientation", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "source", + "getter_name": "source", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(TrashOrigin.values)", + "dart_type_name": "TrashOrigin" + } + }, + { + "name": "playback_style", + "getter_name": "playbackStyle", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(AssetPlaybackStyle.values)", + "dart_type_name": "AssetPlaybackStyle" + } + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id", + "album_id" + ] + } + }, + { + "id": 30, + "references": [ + 1 + ], + "type": "table", + "data": { + "name": "asset_edit_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "asset_id", + "getter_name": "assetId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "remote_asset_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "action", + "getter_name": "action", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(AssetEditAction.values)", + "dart_type_name": "AssetEditAction" + } + }, + { + "name": "parameters", + "getter_name": "parameters", + "moor_type": "blob", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "editParameterConverter", + "dart_type_name": "Map" + } + }, + { + "name": "sequence", + "getter_name": "sequence", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 31, + "references": [], + "type": "table", + "data": { + "name": "settings", + "was_declared_in_moor": false, + "columns": [ + { + "name": "key", + "getter_name": "key", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "value", + "getter_name": "value", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "updated_at", + "getter_name": "updatedAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "key" + ] + } + }, + { + "id": 32, + "references": [ + 19 + ], + "type": "index", + "data": { + "on": 19, + "name": "idx_partner_shared_with_id", + "sql": "CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 33, + "references": [ + 20 + ], + "type": "index", + "data": { + "on": 20, + "name": "idx_lat_lng", + "sql": "CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)", + "unique": false, + "columns": [] + } + }, + { + "id": 34, + "references": [ + 20 + ], + "type": "index", + "data": { + "on": 20, + "name": "idx_remote_exif_city", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_exif_city\nON remote_exif_entity (city) WHERE city IS NOT NULL\n", + "unique": false, + "columns": [] + } + }, + { + "id": 35, + "references": [ + 21 + ], + "type": "index", + "data": { + "on": 21, + "name": "idx_remote_album_asset_album_asset", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 36, + "references": [ + 23 + ], + "type": "index", + "data": { + "on": 23, + "name": "idx_remote_asset_cloud_id", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 37, + "references": [ + 26 + ], + "type": "index", + "data": { + "on": 26, + "name": "idx_person_owner_id", + "sql": "CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 38, + "references": [ + 27 + ], + "type": "index", + "data": { + "on": 27, + "name": "idx_asset_face_person_id", + "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 39, + "references": [ + 27 + ], + "type": "index", + "data": { + "on": 27, + "name": "idx_asset_face_asset_id", + "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 40, + "references": [ + 27 + ], + "type": "index", + "data": { + "on": 27, + "name": "idx_asset_face_visible_person", + "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_visible_person\nON asset_face_entity (person_id, asset_id)\nWHERE is_visible = 1 AND deleted_at IS NULL\n", + "unique": false, + "columns": [] + } + }, + { + "id": 41, + "references": [ + 29 + ], + "type": "index", + "data": { + "on": 29, + "name": "idx_trashed_local_asset_checksum", + "sql": "CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)", + "unique": false, + "columns": [] + } + }, + { + "id": 42, + "references": [ + 29 + ], + "type": "index", + "data": { + "on": 29, + "name": "idx_trashed_local_asset_album", + "sql": "CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 43, + "references": [ + 30 + ], + "type": "index", + "data": { + "on": 30, + "name": "idx_asset_edit_asset_id", + "sql": "CREATE INDEX IF NOT EXISTS idx_asset_edit_asset_id ON asset_edit_entity (asset_id)", + "unique": false, + "columns": [] + } + } + ], + "fixed_sql": [ + { + "name": "user_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"user_entity\" (\"id\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"email\" TEXT NOT NULL, \"has_profile_image\" INTEGER NOT NULL DEFAULT 0 CHECK (\"has_profile_image\" IN (0, 1)), \"profile_changed_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"avatar_color\" INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "remote_asset_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"remote_asset_entity\" (\"name\" TEXT NOT NULL, \"type\" INTEGER NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"width\" INTEGER NULL, \"height\" INTEGER NULL, \"duration_ms\" INTEGER NULL, \"id\" TEXT NOT NULL, \"checksum\" TEXT NOT NULL, \"is_favorite\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_favorite\" IN (0, 1)), \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"local_date_time\" TEXT NULL, \"thumb_hash\" TEXT NULL, \"deleted_at\" TEXT NULL, \"uploaded_at\" TEXT NULL, \"live_photo_video_id\" TEXT NULL, \"visibility\" INTEGER NOT NULL, \"stack_id\" TEXT NULL, \"library_id\" TEXT NULL, \"is_edited\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_edited\" IN (0, 1)), PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "stack_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"stack_entity\" (\"id\" TEXT NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"primary_asset_id\" TEXT NOT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "local_asset_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"local_asset_entity\" (\"name\" TEXT NOT NULL, \"type\" INTEGER NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"width\" INTEGER NULL, \"height\" INTEGER NULL, \"duration_ms\" INTEGER NULL, \"id\" TEXT NOT NULL, \"checksum\" TEXT NULL, \"is_favorite\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_favorite\" IN (0, 1)), \"orientation\" INTEGER NOT NULL DEFAULT 0, \"i_cloud_id\" TEXT NULL, \"adjustment_time\" TEXT NULL, \"latitude\" REAL NULL, \"longitude\" REAL NULL, \"playback_style\" INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "remote_album_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"remote_album_entity\" (\"id\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"description\" TEXT NOT NULL DEFAULT '', \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"thumbnail_asset_id\" TEXT NULL REFERENCES remote_asset_entity (id) ON DELETE SET NULL, \"is_activity_enabled\" INTEGER NOT NULL DEFAULT 1 CHECK (\"is_activity_enabled\" IN (0, 1)), \"order\" INTEGER NOT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "local_album_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"local_album_entity\" (\"id\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"backup_selection\" INTEGER NOT NULL, \"is_ios_shared_album\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_ios_shared_album\" IN (0, 1)), \"linked_remote_album_id\" TEXT NULL REFERENCES remote_album_entity (id) ON DELETE SET NULL, \"marker\" INTEGER NULL CHECK (\"marker\" IN (0, 1)), PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "local_album_asset_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"local_album_asset_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES local_asset_entity (id) ON DELETE CASCADE, \"album_id\" TEXT NOT NULL REFERENCES local_album_entity (id) ON DELETE CASCADE, \"marker\" INTEGER NULL CHECK (\"marker\" IN (0, 1)), PRIMARY KEY (\"asset_id\", \"album_id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "idx_local_album_asset_album_asset", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)" + } + ] + }, + { + "name": "idx_local_asset_checksum", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)" + } + ] + }, + { + "name": "idx_local_asset_cloud_id", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)" + } + ] + }, + { + "name": "idx_local_asset_created_at", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_created_at ON local_asset_entity (created_at)" + } + ] + }, + { + "name": "idx_stack_primary_asset_id", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)" + } + ] + }, + { + "name": "UQ_remote_assets_owner_checksum", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)" + } + ] + }, + { + "name": "UQ_remote_assets_owner_library_checksum", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)" + } + ] + }, + { + "name": "idx_remote_asset_checksum", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)" + } + ] + }, + { + "name": "idx_remote_asset_stack_id", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)" + } + ] + }, + { + "name": "idx_remote_asset_owner_visibility_deleted_created", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_visibility_deleted_created ON remote_asset_entity (owner_id, visibility, deleted_at, created_at DESC)" + } + ] + }, + { + "name": "auth_user_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"auth_user_entity\" (\"id\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"email\" TEXT NOT NULL, \"is_admin\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_admin\" IN (0, 1)), \"has_profile_image\" INTEGER NOT NULL DEFAULT 0 CHECK (\"has_profile_image\" IN (0, 1)), \"profile_changed_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"avatar_color\" INTEGER NOT NULL, \"quota_size_in_bytes\" INTEGER NOT NULL DEFAULT 0, \"quota_usage_in_bytes\" INTEGER NOT NULL DEFAULT 0, \"pin_code\" TEXT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "user_metadata_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"user_metadata_entity\" (\"user_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"key\" INTEGER NOT NULL, \"value\" BLOB NOT NULL, PRIMARY KEY (\"user_id\", \"key\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "partner_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"partner_entity\" (\"shared_by_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"shared_with_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"in_timeline\" INTEGER NOT NULL DEFAULT 0 CHECK (\"in_timeline\" IN (0, 1)), PRIMARY KEY (\"shared_by_id\", \"shared_with_id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "remote_exif_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"remote_exif_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"city\" TEXT NULL, \"state\" TEXT NULL, \"country\" TEXT NULL, \"date_time_original\" TEXT NULL, \"description\" TEXT NULL, \"height\" INTEGER NULL, \"width\" INTEGER NULL, \"exposure_time\" TEXT NULL, \"f_number\" REAL NULL, \"file_size\" INTEGER NULL, \"focal_length\" REAL NULL, \"latitude\" REAL NULL, \"longitude\" REAL NULL, \"iso\" INTEGER NULL, \"make\" TEXT NULL, \"model\" TEXT NULL, \"lens\" TEXT NULL, \"orientation\" TEXT NULL, \"time_zone\" TEXT NULL, \"rating\" INTEGER NULL, \"projection_type\" TEXT NULL, PRIMARY KEY (\"asset_id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "remote_album_asset_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"remote_album_asset_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"album_id\" TEXT NOT NULL REFERENCES remote_album_entity (id) ON DELETE CASCADE, PRIMARY KEY (\"asset_id\", \"album_id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "remote_album_user_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"remote_album_user_entity\" (\"album_id\" TEXT NOT NULL REFERENCES remote_album_entity (id) ON DELETE CASCADE, \"user_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"role\" INTEGER NOT NULL, PRIMARY KEY (\"album_id\", \"user_id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "remote_asset_cloud_id_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"remote_asset_cloud_id_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"cloud_id\" TEXT NULL, \"created_at\" TEXT NULL, \"adjustment_time\" TEXT NULL, \"latitude\" REAL NULL, \"longitude\" REAL NULL, PRIMARY KEY (\"asset_id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "memory_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"memory_entity\" (\"id\" TEXT NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"deleted_at\" TEXT NULL, \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"type\" INTEGER NOT NULL, \"data\" TEXT NOT NULL, \"is_saved\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_saved\" IN (0, 1)), \"memory_at\" TEXT NOT NULL, \"seen_at\" TEXT NULL, \"show_at\" TEXT NULL, \"hide_at\" TEXT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "memory_asset_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"memory_asset_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"memory_id\" TEXT NOT NULL REFERENCES memory_entity (id) ON DELETE CASCADE, PRIMARY KEY (\"asset_id\", \"memory_id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "person_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"person_entity\" (\"id\" TEXT NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"name\" TEXT NOT NULL, \"face_asset_id\" TEXT NULL, \"is_favorite\" INTEGER NOT NULL CHECK (\"is_favorite\" IN (0, 1)), \"is_hidden\" INTEGER NOT NULL CHECK (\"is_hidden\" IN (0, 1)), \"color\" TEXT NULL, \"birth_date\" TEXT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "asset_face_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"asset_face_entity\" (\"id\" TEXT NOT NULL, \"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"person_id\" TEXT NULL REFERENCES person_entity (id) ON DELETE SET NULL, \"image_width\" INTEGER NOT NULL, \"image_height\" INTEGER NOT NULL, \"bounding_box_x1\" INTEGER NOT NULL, \"bounding_box_y1\" INTEGER NOT NULL, \"bounding_box_x2\" INTEGER NOT NULL, \"bounding_box_y2\" INTEGER NOT NULL, \"source_type\" TEXT NOT NULL, \"is_visible\" INTEGER NOT NULL DEFAULT 1 CHECK (\"is_visible\" IN (0, 1)), \"deleted_at\" TEXT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "store_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"store_entity\" (\"id\" INTEGER NOT NULL, \"string_value\" TEXT NULL, \"int_value\" INTEGER NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "trashed_local_asset_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"trashed_local_asset_entity\" (\"name\" TEXT NOT NULL, \"type\" INTEGER NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"width\" INTEGER NULL, \"height\" INTEGER NULL, \"duration_ms\" INTEGER NULL, \"id\" TEXT NOT NULL, \"album_id\" TEXT NOT NULL, \"checksum\" TEXT NULL, \"is_favorite\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_favorite\" IN (0, 1)), \"orientation\" INTEGER NOT NULL DEFAULT 0, \"source\" INTEGER NOT NULL, \"playback_style\" INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (\"id\", \"album_id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "asset_edit_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"asset_edit_entity\" (\"id\" TEXT NOT NULL, \"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"action\" INTEGER NOT NULL, \"parameters\" BLOB NOT NULL, \"sequence\" INTEGER NOT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "settings", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"settings\" (\"key\" TEXT NOT NULL, \"value\" TEXT NOT NULL, \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), PRIMARY KEY (\"key\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "idx_partner_shared_with_id", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)" + } + ] + }, + { + "name": "idx_lat_lng", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)" + } + ] + }, + { + "name": "idx_remote_exif_city", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_exif_city ON remote_exif_entity (city) WHERE city IS NOT NULL" + } + ] + }, + { + "name": "idx_remote_album_asset_album_asset", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)" + } + ] + }, + { + "name": "idx_remote_asset_cloud_id", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)" + } + ] + }, + { + "name": "idx_person_owner_id", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)" + } + ] + }, + { + "name": "idx_asset_face_person_id", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)" + } + ] + }, + { + "name": "idx_asset_face_asset_id", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)" + } + ] + }, + { + "name": "idx_asset_face_visible_person", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_visible_person ON asset_face_entity (person_id, asset_id) WHERE is_visible = 1 AND deleted_at IS NULL" + } + ] + }, + { + "name": "idx_trashed_local_asset_checksum", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)" + } + ] + }, + { + "name": "idx_trashed_local_asset_album", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)" + } + ] + }, + { + "name": "idx_asset_edit_asset_id", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_asset_edit_asset_id ON asset_edit_entity (asset_id)" + } + ] + } + ] +} \ No newline at end of file diff --git a/mobile/drift_schemas/main/drift_schema_v29.json b/mobile/drift_schemas/main/drift_schema_v29.json new file mode 100644 index 0000000000..d40878efc2 --- /dev/null +++ b/mobile/drift_schemas/main/drift_schema_v29.json @@ -0,0 +1,3603 @@ +{ + "_meta": { + "description": "This file contains a serialized version of schema entities for drift.", + "version": "1.3.0" + }, + "options": { + "store_date_time_values_as_text": true + }, + "entities": [ + { + "id": 0, + "references": [], + "type": "table", + "data": { + "name": "user_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "name", + "getter_name": "name", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "email", + "getter_name": "email", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "has_profile_image", + "getter_name": "hasProfileImage", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"has_profile_image\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"has_profile_image\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "profile_changed_at", + "getter_name": "profileChangedAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "avatar_color", + "getter_name": "avatarColor", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(AvatarColor.values)", + "dart_type_name": "AvatarColor" + } + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 1, + "references": [ + 0 + ], + "type": "table", + "data": { + "name": "remote_asset_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "name", + "getter_name": "name", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "type", + "getter_name": "type", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(AssetType.values)", + "dart_type_name": "AssetType" + } + }, + { + "name": "created_at", + "getter_name": "createdAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "updated_at", + "getter_name": "updatedAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "width", + "getter_name": "width", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "height", + "getter_name": "height", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "duration_ms", + "getter_name": "durationMs", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "checksum", + "getter_name": "checksum", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "is_favorite", + "getter_name": "isFavorite", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"is_favorite\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"is_favorite\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "owner_id", + "getter_name": "ownerId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "user_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "local_date_time", + "getter_name": "localDateTime", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "thumb_hash", + "getter_name": "thumbHash", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "deleted_at", + "getter_name": "deletedAt", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "uploaded_at", + "getter_name": "uploadedAt", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "live_photo_video_id", + "getter_name": "livePhotoVideoId", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "visibility", + "getter_name": "visibility", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(AssetVisibility.values)", + "dart_type_name": "AssetVisibility" + } + }, + { + "name": "stack_id", + "getter_name": "stackId", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "library_id", + "getter_name": "libraryId", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "is_edited", + "getter_name": "isEdited", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"is_edited\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"is_edited\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 2, + "references": [ + 0 + ], + "type": "table", + "data": { + "name": "stack_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "created_at", + "getter_name": "createdAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "updated_at", + "getter_name": "updatedAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "owner_id", + "getter_name": "ownerId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "user_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "primary_asset_id", + "getter_name": "primaryAssetId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 3, + "references": [], + "type": "table", + "data": { + "name": "local_asset_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "name", + "getter_name": "name", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "type", + "getter_name": "type", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(AssetType.values)", + "dart_type_name": "AssetType" + } + }, + { + "name": "created_at", + "getter_name": "createdAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "updated_at", + "getter_name": "updatedAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "width", + "getter_name": "width", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "height", + "getter_name": "height", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "duration_ms", + "getter_name": "durationMs", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "checksum", + "getter_name": "checksum", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "is_favorite", + "getter_name": "isFavorite", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"is_favorite\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"is_favorite\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "orientation", + "getter_name": "orientation", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "i_cloud_id", + "getter_name": "iCloudId", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "adjustment_time", + "getter_name": "adjustmentTime", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "latitude", + "getter_name": "latitude", + "moor_type": "double", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "longitude", + "getter_name": "longitude", + "moor_type": "double", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "playback_style", + "getter_name": "playbackStyle", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(AssetPlaybackStyle.values)", + "dart_type_name": "AssetPlaybackStyle" + } + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 4, + "references": [ + 1 + ], + "type": "table", + "data": { + "name": "remote_album_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "name", + "getter_name": "name", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "description", + "getter_name": "description", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('\\'\\'')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "created_at", + "getter_name": "createdAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "updated_at", + "getter_name": "updatedAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "thumbnail_asset_id", + "getter_name": "thumbnailAssetId", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE SET NULL", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE SET NULL" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "remote_asset_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "setNull" + } + } + ] + }, + { + "name": "is_activity_enabled", + "getter_name": "isActivityEnabled", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"is_activity_enabled\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"is_activity_enabled\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('1')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "order", + "getter_name": "order", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(AlbumAssetOrder.values)", + "dart_type_name": "AlbumAssetOrder" + } + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 5, + "references": [ + 4 + ], + "type": "table", + "data": { + "name": "local_album_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "name", + "getter_name": "name", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "updated_at", + "getter_name": "updatedAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "backup_selection", + "getter_name": "backupSelection", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(BackupSelection.values)", + "dart_type_name": "BackupSelection" + } + }, + { + "name": "is_ios_shared_album", + "getter_name": "isIosSharedAlbum", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"is_ios_shared_album\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"is_ios_shared_album\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "linked_remote_album_id", + "getter_name": "linkedRemoteAlbumId", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "defaultConstraints": "REFERENCES remote_album_entity (id) ON DELETE SET NULL", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES remote_album_entity (id) ON DELETE SET NULL" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "remote_album_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "setNull" + } + } + ] + }, + { + "name": "marker", + "getter_name": "marker_", + "moor_type": "bool", + "nullable": true, + "customConstraints": null, + "defaultConstraints": "CHECK (\"marker\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"marker\" IN (0, 1))" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 6, + "references": [ + 3, + 5 + ], + "type": "table", + "data": { + "name": "local_album_asset_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "asset_id", + "getter_name": "assetId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES local_asset_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES local_asset_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "local_asset_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "album_id", + "getter_name": "albumId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES local_album_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES local_album_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "local_album_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "marker", + "getter_name": "marker_", + "moor_type": "bool", + "nullable": true, + "customConstraints": null, + "defaultConstraints": "CHECK (\"marker\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"marker\" IN (0, 1))" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "asset_id", + "album_id" + ] + } + }, + { + "id": 7, + "references": [ + 6 + ], + "type": "index", + "data": { + "on": 6, + "name": "idx_local_album_asset_album_asset", + "sql": "CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 8, + "references": [ + 3 + ], + "type": "index", + "data": { + "on": 3, + "name": "idx_local_asset_checksum", + "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)", + "unique": false, + "columns": [] + } + }, + { + "id": 9, + "references": [ + 3 + ], + "type": "index", + "data": { + "on": 3, + "name": "idx_local_asset_cloud_id", + "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 10, + "references": [ + 3 + ], + "type": "index", + "data": { + "on": 3, + "name": "idx_local_asset_created_at", + "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_created_at ON local_asset_entity (created_at)", + "unique": false, + "columns": [] + } + }, + { + "id": 11, + "references": [ + 2 + ], + "type": "index", + "data": { + "on": 2, + "name": "idx_stack_primary_asset_id", + "sql": "CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 12, + "references": [ + 1 + ], + "type": "index", + "data": { + "on": 1, + "name": "UQ_remote_assets_owner_checksum", + "sql": "CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum\nON remote_asset_entity (owner_id, checksum)\nWHERE (library_id IS NULL);\n", + "unique": true, + "columns": [] + } + }, + { + "id": 13, + "references": [ + 1 + ], + "type": "index", + "data": { + "on": 1, + "name": "UQ_remote_assets_owner_library_checksum", + "sql": "CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum\nON remote_asset_entity (owner_id, library_id, checksum)\nWHERE (library_id IS NOT NULL);\n", + "unique": true, + "columns": [] + } + }, + { + "id": 14, + "references": [ + 1 + ], + "type": "index", + "data": { + "on": 1, + "name": "idx_remote_asset_checksum", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)", + "unique": false, + "columns": [] + } + }, + { + "id": 15, + "references": [ + 1 + ], + "type": "index", + "data": { + "on": 1, + "name": "idx_remote_asset_stack_id", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 16, + "references": [ + 1 + ], + "type": "index", + "data": { + "on": 1, + "name": "idx_remote_asset_owner_visibility_deleted_created", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_visibility_deleted_created\nON remote_asset_entity (owner_id, visibility, deleted_at, created_at DESC)\n", + "unique": false, + "columns": [] + } + }, + { + "id": 17, + "references": [], + "type": "table", + "data": { + "name": "auth_user_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "name", + "getter_name": "name", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "email", + "getter_name": "email", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "is_admin", + "getter_name": "isAdmin", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"is_admin\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"is_admin\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "has_profile_image", + "getter_name": "hasProfileImage", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"has_profile_image\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"has_profile_image\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "profile_changed_at", + "getter_name": "profileChangedAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "avatar_color", + "getter_name": "avatarColor", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(AvatarColor.values)", + "dart_type_name": "AvatarColor" + } + }, + { + "name": "quota_size_in_bytes", + "getter_name": "quotaSizeInBytes", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "quota_usage_in_bytes", + "getter_name": "quotaUsageInBytes", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "pin_code", + "getter_name": "pinCode", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 18, + "references": [ + 0 + ], + "type": "table", + "data": { + "name": "user_metadata_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "user_id", + "getter_name": "userId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "user_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "key", + "getter_name": "key", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(UserMetadataKey.values)", + "dart_type_name": "UserMetadataKey" + } + }, + { + "name": "value", + "getter_name": "value", + "moor_type": "blob", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "userMetadataConverter", + "dart_type_name": "Map" + } + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "user_id", + "key" + ] + } + }, + { + "id": 19, + "references": [ + 0 + ], + "type": "table", + "data": { + "name": "partner_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "shared_by_id", + "getter_name": "sharedById", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "user_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "shared_with_id", + "getter_name": "sharedWithId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "user_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "in_timeline", + "getter_name": "inTimeline", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"in_timeline\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"in_timeline\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "shared_by_id", + "shared_with_id" + ] + } + }, + { + "id": 20, + "references": [ + 1 + ], + "type": "table", + "data": { + "name": "remote_exif_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "asset_id", + "getter_name": "assetId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "remote_asset_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "city", + "getter_name": "city", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "state", + "getter_name": "state", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "country", + "getter_name": "country", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "date_time_original", + "getter_name": "dateTimeOriginal", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "description", + "getter_name": "description", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "height", + "getter_name": "height", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "width", + "getter_name": "width", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "exposure_time", + "getter_name": "exposureTime", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "f_number", + "getter_name": "fNumber", + "moor_type": "double", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "file_size", + "getter_name": "fileSize", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "focal_length", + "getter_name": "focalLength", + "moor_type": "double", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "latitude", + "getter_name": "latitude", + "moor_type": "double", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "longitude", + "getter_name": "longitude", + "moor_type": "double", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "iso", + "getter_name": "iso", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "make", + "getter_name": "make", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "model", + "getter_name": "model", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "lens", + "getter_name": "lens", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "orientation", + "getter_name": "orientation", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "time_zone", + "getter_name": "timeZone", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "rating", + "getter_name": "rating", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "projection_type", + "getter_name": "projectionType", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "asset_id" + ] + } + }, + { + "id": 21, + "references": [ + 1, + 4 + ], + "type": "table", + "data": { + "name": "remote_album_asset_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "asset_id", + "getter_name": "assetId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "remote_asset_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "album_id", + "getter_name": "albumId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES remote_album_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES remote_album_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "remote_album_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "asset_id", + "album_id" + ] + } + }, + { + "id": 22, + "references": [ + 4, + 0 + ], + "type": "table", + "data": { + "name": "remote_album_user_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "album_id", + "getter_name": "albumId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES remote_album_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES remote_album_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "remote_album_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "user_id", + "getter_name": "userId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "user_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "role", + "getter_name": "role", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(AlbumUserRole.values)", + "dart_type_name": "AlbumUserRole" + } + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "album_id", + "user_id" + ] + } + }, + { + "id": 23, + "references": [ + 1 + ], + "type": "table", + "data": { + "name": "remote_asset_cloud_id_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "asset_id", + "getter_name": "assetId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "remote_asset_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "cloud_id", + "getter_name": "cloudId", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "created_at", + "getter_name": "createdAt", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "adjustment_time", + "getter_name": "adjustmentTime", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "latitude", + "getter_name": "latitude", + "moor_type": "double", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "longitude", + "getter_name": "longitude", + "moor_type": "double", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "asset_id" + ] + } + }, + { + "id": 24, + "references": [ + 0 + ], + "type": "table", + "data": { + "name": "memory_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "created_at", + "getter_name": "createdAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "updated_at", + "getter_name": "updatedAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "deleted_at", + "getter_name": "deletedAt", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "owner_id", + "getter_name": "ownerId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "user_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "type", + "getter_name": "type", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(MemoryTypeEnum.values)", + "dart_type_name": "MemoryTypeEnum" + } + }, + { + "name": "data", + "getter_name": "data", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "is_saved", + "getter_name": "isSaved", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"is_saved\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"is_saved\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "memory_at", + "getter_name": "memoryAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "seen_at", + "getter_name": "seenAt", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "show_at", + "getter_name": "showAt", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "hide_at", + "getter_name": "hideAt", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 25, + "references": [ + 1, + 24 + ], + "type": "table", + "data": { + "name": "memory_asset_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "asset_id", + "getter_name": "assetId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "remote_asset_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "memory_id", + "getter_name": "memoryId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES memory_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES memory_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "memory_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "asset_id", + "memory_id" + ] + } + }, + { + "id": 26, + "references": [ + 0 + ], + "type": "table", + "data": { + "name": "person_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "created_at", + "getter_name": "createdAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "updated_at", + "getter_name": "updatedAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "owner_id", + "getter_name": "ownerId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "user_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "name", + "getter_name": "name", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "face_asset_id", + "getter_name": "faceAssetId", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "is_favorite", + "getter_name": "isFavorite", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"is_favorite\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"is_favorite\" IN (0, 1))" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "is_hidden", + "getter_name": "isHidden", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"is_hidden\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"is_hidden\" IN (0, 1))" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "color", + "getter_name": "color", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "birth_date", + "getter_name": "birthDate", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 27, + "references": [ + 1, + 26 + ], + "type": "table", + "data": { + "name": "asset_face_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "asset_id", + "getter_name": "assetId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "remote_asset_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "person_id", + "getter_name": "personId", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "defaultConstraints": "REFERENCES person_entity (id) ON DELETE SET NULL", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES person_entity (id) ON DELETE SET NULL" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "person_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "setNull" + } + } + ] + }, + { + "name": "image_width", + "getter_name": "imageWidth", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "image_height", + "getter_name": "imageHeight", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "bounding_box_x1", + "getter_name": "boundingBoxX1", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "bounding_box_y1", + "getter_name": "boundingBoxY1", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "bounding_box_x2", + "getter_name": "boundingBoxX2", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "bounding_box_y2", + "getter_name": "boundingBoxY2", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "source_type", + "getter_name": "sourceType", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "is_visible", + "getter_name": "isVisible", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"is_visible\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"is_visible\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('1')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "deleted_at", + "getter_name": "deletedAt", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 28, + "references": [], + "type": "table", + "data": { + "name": "store_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "string_value", + "getter_name": "stringValue", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "int_value", + "getter_name": "intValue", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 29, + "references": [], + "type": "table", + "data": { + "name": "trashed_local_asset_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "name", + "getter_name": "name", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "type", + "getter_name": "type", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(AssetType.values)", + "dart_type_name": "AssetType" + } + }, + { + "name": "created_at", + "getter_name": "createdAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "updated_at", + "getter_name": "updatedAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "width", + "getter_name": "width", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "height", + "getter_name": "height", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "duration_ms", + "getter_name": "durationMs", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "album_id", + "getter_name": "albumId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "checksum", + "getter_name": "checksum", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "is_favorite", + "getter_name": "isFavorite", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"is_favorite\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"is_favorite\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "orientation", + "getter_name": "orientation", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "source", + "getter_name": "source", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(TrashOrigin.values)", + "dart_type_name": "TrashOrigin" + } + }, + { + "name": "playback_style", + "getter_name": "playbackStyle", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(AssetPlaybackStyle.values)", + "dart_type_name": "AssetPlaybackStyle" + } + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id", + "album_id" + ] + } + }, + { + "id": 30, + "references": [ + 1 + ], + "type": "table", + "data": { + "name": "asset_edit_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "asset_id", + "getter_name": "assetId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "remote_asset_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "action", + "getter_name": "action", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(AssetEditAction.values)", + "dart_type_name": "AssetEditAction" + } + }, + { + "name": "parameters", + "getter_name": "parameters", + "moor_type": "blob", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "editParameterConverter", + "dart_type_name": "Map" + } + }, + { + "name": "sequence", + "getter_name": "sequence", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 31, + "references": [], + "type": "table", + "data": { + "name": "settings", + "was_declared_in_moor": false, + "columns": [ + { + "name": "key", + "getter_name": "key", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "value", + "getter_name": "value", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "updated_at", + "getter_name": "updatedAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "key" + ] + } + }, + { + "id": 32, + "references": [ + 1 + ], + "type": "table", + "data": { + "name": "asset_ocr_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "asset_id", + "getter_name": "assetId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "remote_asset_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "x1", + "getter_name": "x1", + "moor_type": "double", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "y1", + "getter_name": "y1", + "moor_type": "double", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "x2", + "getter_name": "x2", + "moor_type": "double", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "y2", + "getter_name": "y2", + "moor_type": "double", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "x3", + "getter_name": "x3", + "moor_type": "double", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "y3", + "getter_name": "y3", + "moor_type": "double", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "x4", + "getter_name": "x4", + "moor_type": "double", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "y4", + "getter_name": "y4", + "moor_type": "double", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "box_score", + "getter_name": "boxScore", + "moor_type": "double", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "text_score", + "getter_name": "textScore", + "moor_type": "double", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "recognized_text", + "getter_name": "recognizedText", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "is_visible", + "getter_name": "isVisible", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"is_visible\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"is_visible\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('1')", + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 33, + "references": [ + 19 + ], + "type": "index", + "data": { + "on": 19, + "name": "idx_partner_shared_with_id", + "sql": "CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 34, + "references": [ + 20 + ], + "type": "index", + "data": { + "on": 20, + "name": "idx_lat_lng", + "sql": "CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)", + "unique": false, + "columns": [] + } + }, + { + "id": 35, + "references": [ + 20 + ], + "type": "index", + "data": { + "on": 20, + "name": "idx_remote_exif_city", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_exif_city\nON remote_exif_entity (city) WHERE city IS NOT NULL\n", + "unique": false, + "columns": [] + } + }, + { + "id": 36, + "references": [ + 21 + ], + "type": "index", + "data": { + "on": 21, + "name": "idx_remote_album_asset_album_asset", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 37, + "references": [ + 23 + ], + "type": "index", + "data": { + "on": 23, + "name": "idx_remote_asset_cloud_id", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 38, + "references": [ + 26 + ], + "type": "index", + "data": { + "on": 26, + "name": "idx_person_owner_id", + "sql": "CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 39, + "references": [ + 27 + ], + "type": "index", + "data": { + "on": 27, + "name": "idx_asset_face_person_id", + "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 40, + "references": [ + 27 + ], + "type": "index", + "data": { + "on": 27, + "name": "idx_asset_face_asset_id", + "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 41, + "references": [ + 27 + ], + "type": "index", + "data": { + "on": 27, + "name": "idx_asset_face_visible_person", + "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_visible_person\nON asset_face_entity (person_id, asset_id)\nWHERE is_visible = 1 AND deleted_at IS NULL\n", + "unique": false, + "columns": [] + } + }, + { + "id": 42, + "references": [ + 29 + ], + "type": "index", + "data": { + "on": 29, + "name": "idx_trashed_local_asset_checksum", + "sql": "CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)", + "unique": false, + "columns": [] + } + }, + { + "id": 43, + "references": [ + 29 + ], + "type": "index", + "data": { + "on": 29, + "name": "idx_trashed_local_asset_album", + "sql": "CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 44, + "references": [ + 30 + ], + "type": "index", + "data": { + "on": 30, + "name": "idx_asset_edit_asset_id", + "sql": "CREATE INDEX IF NOT EXISTS idx_asset_edit_asset_id ON asset_edit_entity (asset_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 45, + "references": [ + 32 + ], + "type": "index", + "data": { + "on": 32, + "name": "idx_asset_ocr_asset_id", + "sql": "CREATE INDEX IF NOT EXISTS idx_asset_ocr_asset_id ON asset_ocr_entity (asset_id)", + "unique": false, + "columns": [] + } + } + ], + "fixed_sql": [ + { + "name": "user_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"user_entity\" (\"id\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"email\" TEXT NOT NULL, \"has_profile_image\" INTEGER NOT NULL DEFAULT 0 CHECK (\"has_profile_image\" IN (0, 1)), \"profile_changed_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"avatar_color\" INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "remote_asset_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"remote_asset_entity\" (\"name\" TEXT NOT NULL, \"type\" INTEGER NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"width\" INTEGER NULL, \"height\" INTEGER NULL, \"duration_ms\" INTEGER NULL, \"id\" TEXT NOT NULL, \"checksum\" TEXT NOT NULL, \"is_favorite\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_favorite\" IN (0, 1)), \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"local_date_time\" TEXT NULL, \"thumb_hash\" TEXT NULL, \"deleted_at\" TEXT NULL, \"uploaded_at\" TEXT NULL, \"live_photo_video_id\" TEXT NULL, \"visibility\" INTEGER NOT NULL, \"stack_id\" TEXT NULL, \"library_id\" TEXT NULL, \"is_edited\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_edited\" IN (0, 1)), PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "stack_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"stack_entity\" (\"id\" TEXT NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"primary_asset_id\" TEXT NOT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "local_asset_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"local_asset_entity\" (\"name\" TEXT NOT NULL, \"type\" INTEGER NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"width\" INTEGER NULL, \"height\" INTEGER NULL, \"duration_ms\" INTEGER NULL, \"id\" TEXT NOT NULL, \"checksum\" TEXT NULL, \"is_favorite\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_favorite\" IN (0, 1)), \"orientation\" INTEGER NOT NULL DEFAULT 0, \"i_cloud_id\" TEXT NULL, \"adjustment_time\" TEXT NULL, \"latitude\" REAL NULL, \"longitude\" REAL NULL, \"playback_style\" INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "remote_album_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"remote_album_entity\" (\"id\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"description\" TEXT NOT NULL DEFAULT '', \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"thumbnail_asset_id\" TEXT NULL REFERENCES remote_asset_entity (id) ON DELETE SET NULL, \"is_activity_enabled\" INTEGER NOT NULL DEFAULT 1 CHECK (\"is_activity_enabled\" IN (0, 1)), \"order\" INTEGER NOT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "local_album_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"local_album_entity\" (\"id\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"backup_selection\" INTEGER NOT NULL, \"is_ios_shared_album\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_ios_shared_album\" IN (0, 1)), \"linked_remote_album_id\" TEXT NULL REFERENCES remote_album_entity (id) ON DELETE SET NULL, \"marker\" INTEGER NULL CHECK (\"marker\" IN (0, 1)), PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "local_album_asset_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"local_album_asset_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES local_asset_entity (id) ON DELETE CASCADE, \"album_id\" TEXT NOT NULL REFERENCES local_album_entity (id) ON DELETE CASCADE, \"marker\" INTEGER NULL CHECK (\"marker\" IN (0, 1)), PRIMARY KEY (\"asset_id\", \"album_id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "idx_local_album_asset_album_asset", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)" + } + ] + }, + { + "name": "idx_local_asset_checksum", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)" + } + ] + }, + { + "name": "idx_local_asset_cloud_id", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)" + } + ] + }, + { + "name": "idx_local_asset_created_at", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_created_at ON local_asset_entity (created_at)" + } + ] + }, + { + "name": "idx_stack_primary_asset_id", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)" + } + ] + }, + { + "name": "UQ_remote_assets_owner_checksum", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)" + } + ] + }, + { + "name": "UQ_remote_assets_owner_library_checksum", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)" + } + ] + }, + { + "name": "idx_remote_asset_checksum", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)" + } + ] + }, + { + "name": "idx_remote_asset_stack_id", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)" + } + ] + }, + { + "name": "idx_remote_asset_owner_visibility_deleted_created", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_visibility_deleted_created ON remote_asset_entity (owner_id, visibility, deleted_at, created_at DESC)" + } + ] + }, + { + "name": "auth_user_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"auth_user_entity\" (\"id\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"email\" TEXT NOT NULL, \"is_admin\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_admin\" IN (0, 1)), \"has_profile_image\" INTEGER NOT NULL DEFAULT 0 CHECK (\"has_profile_image\" IN (0, 1)), \"profile_changed_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"avatar_color\" INTEGER NOT NULL, \"quota_size_in_bytes\" INTEGER NOT NULL DEFAULT 0, \"quota_usage_in_bytes\" INTEGER NOT NULL DEFAULT 0, \"pin_code\" TEXT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "user_metadata_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"user_metadata_entity\" (\"user_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"key\" INTEGER NOT NULL, \"value\" BLOB NOT NULL, PRIMARY KEY (\"user_id\", \"key\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "partner_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"partner_entity\" (\"shared_by_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"shared_with_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"in_timeline\" INTEGER NOT NULL DEFAULT 0 CHECK (\"in_timeline\" IN (0, 1)), PRIMARY KEY (\"shared_by_id\", \"shared_with_id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "remote_exif_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"remote_exif_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"city\" TEXT NULL, \"state\" TEXT NULL, \"country\" TEXT NULL, \"date_time_original\" TEXT NULL, \"description\" TEXT NULL, \"height\" INTEGER NULL, \"width\" INTEGER NULL, \"exposure_time\" TEXT NULL, \"f_number\" REAL NULL, \"file_size\" INTEGER NULL, \"focal_length\" REAL NULL, \"latitude\" REAL NULL, \"longitude\" REAL NULL, \"iso\" INTEGER NULL, \"make\" TEXT NULL, \"model\" TEXT NULL, \"lens\" TEXT NULL, \"orientation\" TEXT NULL, \"time_zone\" TEXT NULL, \"rating\" INTEGER NULL, \"projection_type\" TEXT NULL, PRIMARY KEY (\"asset_id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "remote_album_asset_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"remote_album_asset_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"album_id\" TEXT NOT NULL REFERENCES remote_album_entity (id) ON DELETE CASCADE, PRIMARY KEY (\"asset_id\", \"album_id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "remote_album_user_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"remote_album_user_entity\" (\"album_id\" TEXT NOT NULL REFERENCES remote_album_entity (id) ON DELETE CASCADE, \"user_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"role\" INTEGER NOT NULL, PRIMARY KEY (\"album_id\", \"user_id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "remote_asset_cloud_id_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"remote_asset_cloud_id_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"cloud_id\" TEXT NULL, \"created_at\" TEXT NULL, \"adjustment_time\" TEXT NULL, \"latitude\" REAL NULL, \"longitude\" REAL NULL, PRIMARY KEY (\"asset_id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "memory_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"memory_entity\" (\"id\" TEXT NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"deleted_at\" TEXT NULL, \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"type\" INTEGER NOT NULL, \"data\" TEXT NOT NULL, \"is_saved\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_saved\" IN (0, 1)), \"memory_at\" TEXT NOT NULL, \"seen_at\" TEXT NULL, \"show_at\" TEXT NULL, \"hide_at\" TEXT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "memory_asset_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"memory_asset_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"memory_id\" TEXT NOT NULL REFERENCES memory_entity (id) ON DELETE CASCADE, PRIMARY KEY (\"asset_id\", \"memory_id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "person_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"person_entity\" (\"id\" TEXT NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"name\" TEXT NOT NULL, \"face_asset_id\" TEXT NULL, \"is_favorite\" INTEGER NOT NULL CHECK (\"is_favorite\" IN (0, 1)), \"is_hidden\" INTEGER NOT NULL CHECK (\"is_hidden\" IN (0, 1)), \"color\" TEXT NULL, \"birth_date\" TEXT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "asset_face_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"asset_face_entity\" (\"id\" TEXT NOT NULL, \"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"person_id\" TEXT NULL REFERENCES person_entity (id) ON DELETE SET NULL, \"image_width\" INTEGER NOT NULL, \"image_height\" INTEGER NOT NULL, \"bounding_box_x1\" INTEGER NOT NULL, \"bounding_box_y1\" INTEGER NOT NULL, \"bounding_box_x2\" INTEGER NOT NULL, \"bounding_box_y2\" INTEGER NOT NULL, \"source_type\" TEXT NOT NULL, \"is_visible\" INTEGER NOT NULL DEFAULT 1 CHECK (\"is_visible\" IN (0, 1)), \"deleted_at\" TEXT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "store_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"store_entity\" (\"id\" INTEGER NOT NULL, \"string_value\" TEXT NULL, \"int_value\" INTEGER NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "trashed_local_asset_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"trashed_local_asset_entity\" (\"name\" TEXT NOT NULL, \"type\" INTEGER NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"width\" INTEGER NULL, \"height\" INTEGER NULL, \"duration_ms\" INTEGER NULL, \"id\" TEXT NOT NULL, \"album_id\" TEXT NOT NULL, \"checksum\" TEXT NULL, \"is_favorite\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_favorite\" IN (0, 1)), \"orientation\" INTEGER NOT NULL DEFAULT 0, \"source\" INTEGER NOT NULL, \"playback_style\" INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (\"id\", \"album_id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "asset_edit_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"asset_edit_entity\" (\"id\" TEXT NOT NULL, \"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"action\" INTEGER NOT NULL, \"parameters\" BLOB NOT NULL, \"sequence\" INTEGER NOT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "settings", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"settings\" (\"key\" TEXT NOT NULL, \"value\" TEXT NOT NULL, \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), PRIMARY KEY (\"key\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "asset_ocr_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"asset_ocr_entity\" (\"id\" TEXT NOT NULL, \"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"x1\" REAL NOT NULL, \"y1\" REAL NOT NULL, \"x2\" REAL NOT NULL, \"y2\" REAL NOT NULL, \"x3\" REAL NOT NULL, \"y3\" REAL NOT NULL, \"x4\" REAL NOT NULL, \"y4\" REAL NOT NULL, \"box_score\" REAL NOT NULL, \"text_score\" REAL NOT NULL, \"recognized_text\" TEXT NOT NULL, \"is_visible\" INTEGER NOT NULL DEFAULT 1 CHECK (\"is_visible\" IN (0, 1)), PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "idx_partner_shared_with_id", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)" + } + ] + }, + { + "name": "idx_lat_lng", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)" + } + ] + }, + { + "name": "idx_remote_exif_city", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_exif_city ON remote_exif_entity (city) WHERE city IS NOT NULL" + } + ] + }, + { + "name": "idx_remote_album_asset_album_asset", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)" + } + ] + }, + { + "name": "idx_remote_asset_cloud_id", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)" + } + ] + }, + { + "name": "idx_person_owner_id", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)" + } + ] + }, + { + "name": "idx_asset_face_person_id", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)" + } + ] + }, + { + "name": "idx_asset_face_asset_id", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)" + } + ] + }, + { + "name": "idx_asset_face_visible_person", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_visible_person ON asset_face_entity (person_id, asset_id) WHERE is_visible = 1 AND deleted_at IS NULL" + } + ] + }, + { + "name": "idx_trashed_local_asset_checksum", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)" + } + ] + }, + { + "name": "idx_trashed_local_asset_album", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)" + } + ] + }, + { + "name": "idx_asset_edit_asset_id", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_asset_edit_asset_id ON asset_edit_entity (asset_id)" + } + ] + }, + { + "name": "idx_asset_ocr_asset_id", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_asset_ocr_asset_id ON asset_ocr_entity (asset_id)" + } + ] + } + ] +} \ No newline at end of file diff --git a/mobile/integration_test/background_sync_teardown_test.dart b/mobile/integration_test/background_sync_teardown_test.dart new file mode 100644 index 0000000000..0f125b7fcc --- /dev/null +++ b/mobile/integration_test/background_sync_teardown_test.dart @@ -0,0 +1,154 @@ +import 'dart:async'; + +import 'package:drift/drift.dart' show Value; +import 'package:flutter_test/flutter_test.dart'; +import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/domain/utils/background_sync.dart'; +import 'package:immich_mobile/entities/store.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/user.entity.drift.dart'; +import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; +import 'package:immich_mobile/main.dart' as app; +import 'package:immich_mobile/services/api.service.dart'; +import 'package:immich_mobile/utils/bootstrap.dart'; +import 'package:immich_mobile/wm_executor.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:openapi/api.dart'; + +import 'test_utils/fake_immich_server.dart'; + +void main() { + final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + // These tests do real I/O without pumping a widget tree, so disable the fake async clock + binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive; + + late Drift drift; + late FakeImmichServer server; + + setUpAll(() async { + await app.initApp(); + (drift, _) = await Bootstrap.initDomain(); + }); + + setUp(() async { + await workerManagerPatch.init(dynamicSpawning: true); + server = await FakeImmichServer.start(); + await ApiService().resolveAndSetEndpoint(server.endpoint); + await drift.delete(drift.userEntity).go(); + await Store.delete(StoreKey.syncMigrationStatus); + }); + + tearDown(() async { + await workerManagerPatch.dispose(); + await server.close(); + await Store.delete(StoreKey.serverEndpoint); + await Store.delete(StoreKey.syncMigrationStatus); + }); + + void sendUser(SyncStream stream, String id, String name) { + stream.send( + type: SyncEntityType.userV1.value, + data: SyncUserV1( + id: id, + name: name, + email: '$id@test.com', + hasProfileImage: false, + deletedAt: null, + profileChangedAt: DateTime.utc(2025), + ).toJson(), + ack: id, + ); + } + + Future dbReadable() async { + try { + await drift.customSelect('SELECT 1').get().timeout(const Duration(seconds: 5)); + return true; + } catch (_) { + return false; + } + } + + Future userCount() async => (await drift.select(drift.userEntity).get()).length; + + // Starts a remote sync and resolves once its /sync/stream request is open. + Future<(Future, SyncStream)> startSync() async { + final sync = BackgroundSyncManager().syncRemote(); + final stream = await server.streamOpened.timeout( + const Duration(seconds: 30), + onTimeout: () => fail('sync isolate never opened /sync/stream'), + ); + return (sync, stream); + } + + testWidgets('a full sync ingests streamed events into the shared DB', (tester) async { + expect(await userCount(), 0); + + final (sync, stream) = await startSync(); + + sendUser(stream, 'u1', 'Alice'); + sendUser(stream, 'u2', 'Bob'); + await stream.close(); + + final result = await sync.timeout( + const Duration(seconds: 30), + onTimeout: () => fail('sync did not complete after the stream ended'), + ); + expect(result, isTrue); + expect(await userCount(), 2); + expect(server.ackRequests, greaterThan(0)); + }); + + testWidgets('disposing the pool during an in-flight sync drains promptly', (tester) async { + final (sync, _) = await startSync(); + + final sw = Stopwatch()..start(); + await workerManagerPatch.dispose().timeout( + const Duration(seconds: 15), + onTimeout: () => fail('dispose() hung โ€” worker did not drain and exit'), + ); + expect(sw.elapsed, lessThan(const Duration(seconds: 10)), reason: 'abort-driven, not socket-timeout bound'); + + expect(await sync.timeout(const Duration(seconds: 5), onTimeout: () => false), isFalse); + }); + + testWidgets('tearing down a worker blocked mid-write leaves the DB usable', (tester) async { + final (sync, stream) = await startSync(); + + // Hold an exclusive write transaction so the worker's write is blocked. The lock is taken only + // after the stream opens to avoid blocking the worker's own startup DB reads. + final releaseTxn = Completer(); + final txnHeld = Completer(); + final txn = drift.transaction(() async { + await drift.into(drift.userEntity).insert( + UserEntityCompanion.insert( + id: 'holder', + name: 'holder', + email: 'holder@test.com', + hasProfileImage: const Value(false), + profileChangedAt: Value(DateTime.utc(2025)), + ), + ); + txnHeld.complete(); + await releaseTxn.future; + }); + await txnHeld.future; + + sendUser(stream, 'u1', 'Alice'); + await stream.close(); + + // dispose() can only finish once the worker unwinds, which is blocked on the + // lock โ€” so start it, release the lock, then await completion. + final disposed = workerManagerPatch.dispose(); + releaseTxn.complete(); + await txn; + await disposed.timeout( + const Duration(seconds: 15), + onTimeout: () => fail('dispose() hung after releasing the write lock'), + ); + await sync.timeout(const Duration(seconds: 5), onTimeout: () => false); + + expect(await dbReadable(), isTrue); + final users = await drift.select(drift.userEntity).get(); + expect(users.map((u) => u.id), contains('holder')); + }); +} diff --git a/mobile/integration_test/test_utils/fake_immich_server.dart b/mobile/integration_test/test_utils/fake_immich_server.dart new file mode 100644 index 0000000000..c434f83bc5 --- /dev/null +++ b/mobile/integration_test/test_utils/fake_immich_server.dart @@ -0,0 +1,115 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +/// A dummy localhost server that implements only the endpoints that remote-sync touches. +class FakeImmichServer { + FakeImmichServer._(this._server, this.version); + + final HttpServer _server; + final (int, int, int) version; + + final Completer _streamOpened = Completer(); + + int ackRequests = 0; + + String get endpoint => 'http://${_server.address.host}:${_server.port}/api'; + + /// Resolves when the sync isolate opens `POST /sync/stream`. + Future get streamOpened => _streamOpened.future; + + static Future start({(int, int, int) version = (3, 0, 0)}) async { + final server = await HttpServer.bind(InternetAddress.loopbackIPv4, 0); + final fake = FakeImmichServer._(server, version); + fake._listen(); + return fake; + } + + void _listen() { + // A connection torn down mid-write during teardown is expected + _server.listen((request) => unawaited(_route(request).catchError((_) {}))); + } + + Future _route(HttpRequest request) async { + final method = request.method; + final path = request.uri.path; + + if (method == 'GET' && path == '/api/server/ping') { + return _respondJson(request, {'res': 'pong'}); + } + if (method == 'GET' && path == '/api/server/version') { + final (major, minor, patch) = version; + return _respondJson(request, {'major': major, 'minor': minor, 'patch': patch}); + } + if (path == '/api/sync/ack') { + if (method != 'DELETE') { + ackRequests++; + } + return _respondEmpty(request); + } + if (method == 'POST' && path == '/api/sync/stream') { + return _openSyncStream(request); + } + return _respondEmpty(request, status: HttpStatus.notFound); + } + + Future _openSyncStream(HttpRequest request) async { + await request.drain(); + request.response + ..statusCode = HttpStatus.ok + ..headers.contentType = ContentType('application', 'jsonlines+json') + ..contentLength = -1 // chunked: stays open to stream incrementally + ..bufferOutput = false; + // Flush headers so the client's send() resolves and enters its read loop. + await request.response.flush(); + if (!_streamOpened.isCompleted) { + _streamOpened.complete(SyncStream._(request.response)); + } + } + + Future _respondJson(HttpRequest request, Object body) async { + await request.drain(); + request.response + ..statusCode = HttpStatus.ok + ..headers.contentType = ContentType.json + ..write(jsonEncode(body)); + await request.response.close(); + } + + Future _respondEmpty(HttpRequest request, {int status = HttpStatus.ok}) async { + await request.drain(); + request.response.statusCode = status; + await request.response.close(); + } + + Future close() async { + if (_streamOpened.isCompleted) { + await (await _streamOpened.future).close(); + } + await _server.close(force: true); + } +} + +/// Handle to the open `/sync/stream` response: push jsonlines events, then end. +class SyncStream { + SyncStream._(this._response); + + final HttpResponse _response; + bool _closed = false; + + /// [data] should be a Sync*V1 DTO's `toJson()` so the parser's `fromJson` round-trips it. + void send({required String type, required Object data, required String ack}) { + if (_closed) { + return; + } + _response.write('${jsonEncode({'type': type, 'data': data, 'ack': ack})}\n'); + } + + Future close() async { + if (_closed) { + return; + } + _closed = true; + await _response.close(); + } +} diff --git a/mobile/ios/Podfile.lock b/mobile/ios/Podfile.lock index c566d37182..5d966e0033 100644 --- a/mobile/ios/Podfile.lock +++ b/mobile/ios/Podfile.lock @@ -1,58 +1,23 @@ PODS: - - background_downloader (0.0.1): - - Flutter - bonsoir_darwin (0.0.1): - Flutter - FlutterMacOS - - connectivity_plus (0.0.1): - - Flutter - cupertino_http (0.0.1): - Flutter - FlutterMacOS - - device_info_plus (0.0.1): - - Flutter - Flutter (1.0.0) - flutter_local_notifications (0.0.1): - Flutter - - flutter_native_splash (2.4.3): - - Flutter - flutter_secure_storage (6.0.0): - Flutter - - flutter_udid (0.0.1): - - Flutter - - KeychainAccess - - flutter_web_auth_2 (5.0.0): - - Flutter - fluttertoast (0.0.2): - Flutter - - geolocator_apple (1.2.0): - - Flutter - - FlutterMacOS - home_widget (0.0.1): - Flutter - - image_picker_ios (0.0.1): - - Flutter - - integration_test (0.0.1): - - Flutter - - KeychainAccess (4.2.2) - - local_auth_darwin (0.0.1): - - Flutter - - FlutterMacOS - - MapLibre (6.14.0) - - maplibre_gl (0.0.1): - - Flutter - - MapLibre (= 6.14.0) - native_video_player (1.0.0): - Flutter - - network_info_plus (0.0.1): - - Flutter - - package_info_plus (0.4.5): - - Flutter - permission_handler_apple (9.3.0): - Flutter - - photo_manager (3.9.0): - - Flutter - - FlutterMacOS - share_handler_ios (0.0.14): - Flutter - share_handler_ios/share_handler_ios_models (= 0.0.14) @@ -61,144 +26,56 @@ PODS: - Flutter - share_handler_ios_models - share_handler_ios_models (0.0.9) - - share_plus (0.0.1): - - Flutter - - shared_preferences_foundation (0.0.1): - - Flutter - - FlutterMacOS - - url_launcher_ios (0.0.1): - - Flutter - - wakelock_plus (0.0.1): - - Flutter DEPENDENCIES: - - background_downloader (from `.symlinks/plugins/background_downloader/ios`) - bonsoir_darwin (from `.symlinks/plugins/bonsoir_darwin/darwin`) - - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`) - cupertino_http (from `.symlinks/plugins/cupertino_http/darwin`) - - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - Flutter (from `Flutter`) - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) - - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`) - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) - - flutter_udid (from `.symlinks/plugins/flutter_udid/ios`) - - flutter_web_auth_2 (from `.symlinks/plugins/flutter_web_auth_2/ios`) - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`) - - geolocator_apple (from `.symlinks/plugins/geolocator_apple/darwin`) - home_widget (from `.symlinks/plugins/home_widget/ios`) - - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) - - integration_test (from `.symlinks/plugins/integration_test/ios`) - - local_auth_darwin (from `.symlinks/plugins/local_auth_darwin/darwin`) - - maplibre_gl (from `.symlinks/plugins/maplibre_gl/ios`) - native_video_player (from `.symlinks/plugins/native_video_player/ios`) - - network_info_plus (from `.symlinks/plugins/network_info_plus/ios`) - - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) - - photo_manager (from `.symlinks/plugins/photo_manager/darwin`) - share_handler_ios (from `.symlinks/plugins/share_handler_ios/ios`) - share_handler_ios_models (from `.symlinks/plugins/share_handler_ios/ios/Models`) - - share_plus (from `.symlinks/plugins/share_plus/ios`) - - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) - - wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`) - -SPEC REPOS: - trunk: - - KeychainAccess - - MapLibre EXTERNAL SOURCES: - background_downloader: - :path: ".symlinks/plugins/background_downloader/ios" bonsoir_darwin: :path: ".symlinks/plugins/bonsoir_darwin/darwin" - connectivity_plus: - :path: ".symlinks/plugins/connectivity_plus/ios" cupertino_http: :path: ".symlinks/plugins/cupertino_http/darwin" - device_info_plus: - :path: ".symlinks/plugins/device_info_plus/ios" Flutter: :path: Flutter flutter_local_notifications: :path: ".symlinks/plugins/flutter_local_notifications/ios" - flutter_native_splash: - :path: ".symlinks/plugins/flutter_native_splash/ios" flutter_secure_storage: :path: ".symlinks/plugins/flutter_secure_storage/ios" - flutter_udid: - :path: ".symlinks/plugins/flutter_udid/ios" - flutter_web_auth_2: - :path: ".symlinks/plugins/flutter_web_auth_2/ios" fluttertoast: :path: ".symlinks/plugins/fluttertoast/ios" - geolocator_apple: - :path: ".symlinks/plugins/geolocator_apple/darwin" home_widget: :path: ".symlinks/plugins/home_widget/ios" - image_picker_ios: - :path: ".symlinks/plugins/image_picker_ios/ios" - integration_test: - :path: ".symlinks/plugins/integration_test/ios" - local_auth_darwin: - :path: ".symlinks/plugins/local_auth_darwin/darwin" - maplibre_gl: - :path: ".symlinks/plugins/maplibre_gl/ios" native_video_player: :path: ".symlinks/plugins/native_video_player/ios" - network_info_plus: - :path: ".symlinks/plugins/network_info_plus/ios" - package_info_plus: - :path: ".symlinks/plugins/package_info_plus/ios" permission_handler_apple: :path: ".symlinks/plugins/permission_handler_apple/ios" - photo_manager: - :path: ".symlinks/plugins/photo_manager/darwin" share_handler_ios: :path: ".symlinks/plugins/share_handler_ios/ios" share_handler_ios_models: :path: ".symlinks/plugins/share_handler_ios/ios/Models" - share_plus: - :path: ".symlinks/plugins/share_plus/ios" - shared_preferences_foundation: - :path: ".symlinks/plugins/shared_preferences_foundation/darwin" - url_launcher_ios: - :path: ".symlinks/plugins/url_launcher_ios/ios" - wakelock_plus: - :path: ".symlinks/plugins/wakelock_plus/ios" SPEC CHECKSUMS: - background_downloader: 50e91d979067b82081aba359d7d916b3ba5fadad bonsoir_darwin: 29c7ccf356646118844721f36e1de4b61f6cbd0e - connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd cupertino_http: 94ac07f5ff090b8effa6c5e2c47871d48ab7c86c - device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 flutter_local_notifications: ad39620c743ea4c15127860f4b5641649a988100 - flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13 - flutter_udid: 92a5d31fe0526b7b6002a2318df702e12e7eb300 - flutter_web_auth_2: 646fc9df97a01c59e5eea99b237da2c6360f8439 fluttertoast: 2c67e14dce98bbdb200df9e1acf610d7a6264ea1 - geolocator_apple: ab36aa0e8b7d7a2d7639b3b4e48308394e8cef5e home_widget: f169fc41fd807b4d46ab6615dc44d62adbf9f64f - image_picker_ios: e0ece4aa2a75771a7de3fa735d26d90817041326 - integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e - KeychainAccess: c0c4f7f38f6fc7bbe58f5702e25f7bd2f65abf51 - local_auth_darwin: c3ee6cce0a8d56be34c8ccb66ba31f7f180aaebb - MapLibre: 69e572367f4ef6287e18246cfafc39c80cdcabcd - maplibre_gl: 3c924e44725147b03dda33430ad216005b40555f native_video_player: b65c58951ede2f93d103a25366bdebca95081265 - network_info_plus: cf61925ab5205dce05a4f0895989afdb6aade5fc - package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d - photo_manager: 25fd77df14f4f0ba5ef99e2c61814dde77e2bceb share_handler_ios: e2244e990f826b2c8eaa291ac3831569438ba0fb share_handler_ios_models: fc638c9b4330dc7f082586c92aee9dfa0b87b871 - share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a - shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb - url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b - wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556 PODFILE CHECKSUM: 938abbae4114b9c2140c550a2a0d8f7c674f5dfe diff --git a/mobile/ios/Runner.xcodeproj/project.pbxproj b/mobile/ios/Runner.xcodeproj/project.pbxproj index fbffa69ba5..42926ef45e 100644 --- a/mobile/ios/Runner.xcodeproj/project.pbxproj +++ b/mobile/ios/Runner.xcodeproj/project.pbxproj @@ -9,7 +9,7 @@ /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 3B6A31FED0FC846D6BD69BBC /* Pods_ShareExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 357FC57E54FD0F51795CF28A /* Pods_ShareExtension.framework */; }; + 467DA6EAF83F3481F8BD94AB /* Pods_ShareExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8AB817AA297EDEC88B23F3F6 /* Pods_ShareExtension.framework */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; @@ -19,8 +19,10 @@ B21E34AC2E5B09190031FDB9 /* BackgroundWorker.swift in Sources */ = {isa = PBXBuildFile; fileRef = B21E34AB2E5B09100031FDB9 /* BackgroundWorker.swift */; }; B25D377A2E72CA15008B6CA7 /* Connectivity.g.swift in Sources */ = {isa = PBXBuildFile; fileRef = B25D37782E72CA15008B6CA7 /* Connectivity.g.swift */; }; B25D377C2E72CA26008B6CA7 /* ConnectivityApiImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = B25D377B2E72CA20008B6CA7 /* ConnectivityApiImpl.swift */; }; + B2EE00022E72CA15008B6CA7 /* PermissionApi.g.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2EE00012E72CA15008B6CA7 /* PermissionApi.g.swift */; }; + B2EE00042E72CA15008B6CA7 /* PermissionApiImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2EE00032E72CA15008B6CA7 /* PermissionApiImpl.swift */; }; B2BE315F2E5E5229006EEF88 /* BackgroundWorker.g.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2BE315E2E5E5229006EEF88 /* BackgroundWorker.g.swift */; }; - D218389C4A4C4693F141F7D1 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 886774DBDDE6B35BF2B4F2CD /* Pods_Runner.framework */; }; + D3BED739C0BC29BB32E18EB2 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CC499FBCE6B29B2DAFED7130 /* Pods_Runner.framework */; }; F02538E92DFBCBDD008C3FA3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; F0B57D3A2DF764BD00DC5BCC /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F0B57D392DF764BD00DC5BCC /* WidgetKit.framework */; }; F0B57D3C2DF764BD00DC5BCC /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F0B57D3B2DF764BD00DC5BCC /* SwiftUI.framework */; }; @@ -37,6 +39,7 @@ FEE084F82EC172460045228E /* SQLiteData in Frameworks */ = {isa = PBXBuildFile; productRef = FEE084F72EC172460045228E /* SQLiteData */; }; FEE084FB2EC1725A0045228E /* RawStructuredFieldValues in Frameworks */ = {isa = PBXBuildFile; productRef = FEE084FA2EC1725A0045228E /* RawStructuredFieldValues */; }; FEE084FD2EC1725A0045228E /* StructuredFieldValues in Frameworks */ = {isa = PBXBuildFile; productRef = FEE084FC2EC1725A0045228E /* StructuredFieldValues */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -82,16 +85,18 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 10B378D23F917891A0F23E33 /* Pods-ShareExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShareExtension.release.xcconfig"; path = "Target Support Files/Pods-ShareExtension/Pods-ShareExtension.release.xcconfig"; sourceTree = ""; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; - 2E3441B73560D0F6FD25E04F /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; - 357FC57E54FD0F51795CF28A /* Pods_ShareExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ShareExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 571EAA93D77181C7C98C2EA6 /* Pods-ShareExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShareExtension.release.xcconfig"; path = "Target Support Files/Pods-ShareExtension/Pods-ShareExtension.release.xcconfig"; sourceTree = ""; }; + 614A7F5DC5DB09E89E4FCBE8 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 681FBA560D5D2ADDE4F0B59E /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 6D160F04A389B9FFBC557803 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; - 886774DBDDE6B35BF2B4F2CD /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 8AB817AA297EDEC88B23F3F6 /* Pods_ShareExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ShareExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 937632897A02DE9C249F20A6 /* Pods-ShareExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShareExtension.debug.xcconfig"; path = "Target Support Files/Pods-ShareExtension/Pods-ShareExtension.debug.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Immich-Debug.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Immich-Debug.app"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -100,18 +105,18 @@ 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; A01DD6982F7F43B40049AB63 /* ImageRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageRequest.swift; sourceTree = ""; }; - B1FBA9EE014DE20271B0FE77 /* Pods-ShareExtension.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShareExtension.profile.xcconfig"; path = "Target Support Files/Pods-ShareExtension/Pods-ShareExtension.profile.xcconfig"; sourceTree = ""; }; B21E34A92E5AFD210031FDB9 /* BackgroundWorkerApiImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundWorkerApiImpl.swift; sourceTree = ""; }; B21E34AB2E5B09100031FDB9 /* BackgroundWorker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundWorker.swift; sourceTree = ""; }; B25D37782E72CA15008B6CA7 /* Connectivity.g.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Connectivity.g.swift; sourceTree = ""; }; B25D377B2E72CA20008B6CA7 /* ConnectivityApiImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectivityApiImpl.swift; sourceTree = ""; }; + B2EE00012E72CA15008B6CA7 /* PermissionApi.g.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionApi.g.swift; sourceTree = ""; }; + B2EE00032E72CA15008B6CA7 /* PermissionApiImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionApiImpl.swift; sourceTree = ""; }; B2BE315E2E5E5229006EEF88 /* BackgroundWorker.g.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundWorker.g.swift; sourceTree = ""; }; - E0E99CDC17B3EB7FA8BA2332 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + C4A6A71F33CE37B3C913115C /* Pods-ShareExtension.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShareExtension.profile.xcconfig"; path = "Target Support Files/Pods-ShareExtension/Pods-ShareExtension.profile.xcconfig"; sourceTree = ""; }; + CC499FBCE6B29B2DAFED7130 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; F0B57D382DF764BD00DC5BCC /* WidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = WidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; F0B57D392DF764BD00DC5BCC /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; }; F0B57D3B2DF764BD00DC5BCC /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; }; - F7101BB0391A314774615E89 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; - F8A35EA3C3E01BD66AFDE0E5 /* Pods-ShareExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShareExtension.debug.xcconfig"; path = "Target Support Files/Pods-ShareExtension/Pods-ShareExtension.debug.xcconfig"; sourceTree = ""; }; FA9973382CF6DF4B000EF859 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; FAC6F8902D287C890078CB2F /* ShareExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ShareExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; FAC6F8B12D287F120078CB2F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -125,6 +130,7 @@ FE5499F72F1198DE006016CB /* RemoteImagesImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteImagesImpl.swift; sourceTree = ""; }; FE5FE4AD2F30FBC000A71243 /* ImageProcessing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageProcessing.swift; sourceTree = ""; }; FEAFA8722E4D42F4001E47FE /* Thumbhash.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Thumbhash.swift; sourceTree = ""; }; + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ @@ -189,10 +195,11 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, FEE084F82EC172460045228E /* SQLiteData in Frameworks */, FEE084FB2EC1725A0045228E /* RawStructuredFieldValues in Frameworks */, FEE084FD2EC1725A0045228E /* StructuredFieldValues in Frameworks */, - D218389C4A4C4693F141F7D1 /* Pods_Runner.framework in Frameworks */, + D3BED739C0BC29BB32E18EB2 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -209,7 +216,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 3B6A31FED0FC846D6BD69BBC /* Pods_ShareExtension.framework in Frameworks */, + 467DA6EAF83F3481F8BD94AB /* Pods_ShareExtension.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -219,12 +226,12 @@ 0FB772A5B9601143383626CA /* Pods */ = { isa = PBXGroup; children = ( - 2E3441B73560D0F6FD25E04F /* Pods-Runner.debug.xcconfig */, - E0E99CDC17B3EB7FA8BA2332 /* Pods-Runner.release.xcconfig */, - F7101BB0391A314774615E89 /* Pods-Runner.profile.xcconfig */, - F8A35EA3C3E01BD66AFDE0E5 /* Pods-ShareExtension.debug.xcconfig */, - 571EAA93D77181C7C98C2EA6 /* Pods-ShareExtension.release.xcconfig */, - B1FBA9EE014DE20271B0FE77 /* Pods-ShareExtension.profile.xcconfig */, + 614A7F5DC5DB09E89E4FCBE8 /* Pods-Runner.debug.xcconfig */, + 6D160F04A389B9FFBC557803 /* Pods-Runner.release.xcconfig */, + 681FBA560D5D2ADDE4F0B59E /* Pods-Runner.profile.xcconfig */, + 937632897A02DE9C249F20A6 /* Pods-ShareExtension.debug.xcconfig */, + 10B378D23F917891A0F23E33 /* Pods-ShareExtension.release.xcconfig */, + C4A6A71F33CE37B3C913115C /* Pods-ShareExtension.profile.xcconfig */, ); path = Pods; sourceTree = ""; @@ -232,10 +239,10 @@ 1754452DD81DA6620E279E51 /* Frameworks */ = { isa = PBXGroup; children = ( - 886774DBDDE6B35BF2B4F2CD /* Pods_Runner.framework */, - 357FC57E54FD0F51795CF28A /* Pods_ShareExtension.framework */, F0B57D392DF764BD00DC5BCC /* WidgetKit.framework */, F0B57D3B2DF764BD00DC5BCC /* SwiftUI.framework */, + CC499FBCE6B29B2DAFED7130 /* Pods_Runner.framework */, + 8AB817AA297EDEC88B23F3F6 /* Pods_ShareExtension.framework */, ); name = Frameworks; sourceTree = ""; @@ -243,6 +250,7 @@ 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */, 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, @@ -283,6 +291,7 @@ B25D37792E72CA15008B6CA7 /* Connectivity */, B21E34A62E5AF9760031FDB9 /* Background */, B2CF7F8C2DDE4EBB00744BF6 /* Sync */, + B2EE00052E72CA15008B6CA7 /* Permission */, FA9973382CF6DF4B000EF859 /* Runner.entitlements */, FAC7416727DB9F5500C668D8 /* RunnerProfile.entitlements */, 97C146FA1CF9000F007C117D /* Main.storyboard */, @@ -317,6 +326,15 @@ path = Connectivity; sourceTree = ""; }; + B2EE00052E72CA15008B6CA7 /* Permission */ = { + isa = PBXGroup; + children = ( + B2EE00032E72CA15008B6CA7 /* PermissionApiImpl.swift */, + B2EE00012E72CA15008B6CA7 /* PermissionApi.g.swift */, + ); + path = Permission; + sourceTree = ""; + }; FAC6F8B62D287F120078CB2F /* ShareExtension */ = { isa = PBXGroup; children = ( @@ -346,10 +364,13 @@ /* Begin PBXNativeTarget section */ 97C146ED1CF9000F007C117D /* Runner */ = { + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - 4044AF030EF7D8721844FFBA /* [CP] Check Pods Manifest.lock */, + BAEA01ACA3F5C9CD3D732370 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, @@ -357,8 +378,8 @@ 9705A1C41CF9048500538489 /* Embed Frameworks */, FAC6F89A2D287C890078CB2F /* Embed Foundation Extensions */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - D218A34AEE62BC1EF119F5B0 /* [CP] Embed Pods Frameworks */, - 6724EEB7D74949FA08581154 /* [CP] Copy Pods Resources */, + 513DA7292DED6106813332F4 /* [CP] Embed Pods Frameworks */, + 2FA39DEC809D6D7C4A01EFCB /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -372,6 +393,9 @@ FEE084F22EC172080045228E /* Schemas */, ); name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Immich-Debug.app */; productType = "com.apple.product-type.application"; @@ -400,7 +424,7 @@ isa = PBXNativeTarget; buildConfigurationList = FAC6F8A02D287C890078CB2F /* Build configuration list for PBXNativeTarget "ShareExtension" */; buildPhases = ( - 3BEF3D71D97E337D921C0EB5 /* [CP] Check Pods Manifest.lock */, + 8EC9CF3E20AF32BF24D4F3E1 /* [CP] Check Pods Manifest.lock */, FAC6F88C2D287C890078CB2F /* Sources */, FAC6F88D2D287C890078CB2F /* Frameworks */, FAC6F88E2D287C890078CB2F /* Resources */, @@ -449,6 +473,7 @@ ); mainGroup = 97C146E51CF9000F007C117D; packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */, FEE084F62EC172460045228E /* XCRemoteSwiftPackageReference "sqlite-data" */, FEE084F92EC1725A0045228E /* XCRemoteSwiftPackageReference "swift-http-structured-headers" */, ); @@ -495,6 +520,23 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 2FA39DEC809D6D7C4A01EFCB /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -511,7 +553,24 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; - 3BEF3D71D97E337D921C0EB5 /* [CP] Check Pods Manifest.lock */ = { + 513DA7292DED6106813332F4 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 8EC9CF3E20AF32BF24D4F3E1 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -533,7 +592,22 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 4044AF030EF7D8721844FFBA /* [CP] Check Pods Manifest.lock */ = { + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build\n"; + }; + BAEA01ACA3F5C9CD3D732370 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -555,55 +629,6 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 6724EEB7D74949FA08581154 /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Copy Pods Resources"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; - 9740EEB61CF901F6004384FC /* Run Script */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Run Script"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build\n"; - }; - D218A34AEE62BC1EF119F5B0 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -619,6 +644,8 @@ FE5499F42F1197D8006016CB /* RemoteImages.g.swift in Sources */, FE5FE4AE2F30FBC000A71243 /* ImageProcessing.swift in Sources */, B25D377A2E72CA15008B6CA7 /* Connectivity.g.swift in Sources */, + B2EE00022E72CA15008B6CA7 /* PermissionApi.g.swift in Sources */, + B2EE00042E72CA15008B6CA7 /* PermissionApiImpl.swift in Sources */, FE5499F82F1198E2006016CB /* RemoteImagesImpl.swift in Sources */, FEAFA8732E4D42F4001E47FE /* Thumbhash.swift in Sources */, B25D377C2E72CA26008B6CA7 /* ConnectivityApiImpl.swift in Sources */, @@ -718,6 +745,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; + CUSTOM_GROUP_ID = group.app.immich.share.profile; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -750,7 +778,6 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 240; - CUSTOM_GROUP_ID = group.app.immich.share; DEVELOPMENT_TEAM = 2W7AC6T8T5; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; @@ -801,6 +828,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; + CUSTOM_GROUP_ID = group.app.immich.share.debug; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -860,6 +888,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; + CUSTOM_GROUP_ID = group.app.immich.share; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -894,7 +923,6 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 240; - CUSTOM_GROUP_ID = group.app.immich.share; DEVELOPMENT_TEAM = 2W7AC6T8T5; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; @@ -924,7 +952,6 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 240; - CUSTOM_GROUP_ID = group.app.immich.share; DEVELOPMENT_TEAM = 2W7AC6T8T5; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; @@ -1068,7 +1095,7 @@ }; FAC6F89C2D287C890078CB2F /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = F8A35EA3C3E01BD66AFDE0E5 /* Pods-ShareExtension.debug.xcconfig */; + baseConfigurationReference = 937632897A02DE9C249F20A6 /* Pods-ShareExtension.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -1080,7 +1107,6 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 240; - CUSTOM_GROUP_ID = group.app.immich.share; DEVELOPMENT_TEAM = 2W7AC6T8T5; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -1112,7 +1138,7 @@ }; FAC6F89D2D287C890078CB2F /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 571EAA93D77181C7C98C2EA6 /* Pods-ShareExtension.release.xcconfig */; + baseConfigurationReference = 10B378D23F917891A0F23E33 /* Pods-ShareExtension.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -1124,7 +1150,6 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 240; - CUSTOM_GROUP_ID = group.app.immich.share; DEVELOPMENT_TEAM = 2W7AC6T8T5; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -1153,7 +1178,7 @@ }; FAC6F89E2D287C890078CB2F /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = B1FBA9EE014DE20271B0FE77 /* Pods-ShareExtension.profile.xcconfig */; + baseConfigurationReference = C4A6A71F33CE37B3C913115C /* Pods-ShareExtension.profile.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -1165,7 +1190,6 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 240; - CUSTOM_GROUP_ID = group.app.immich.share; DEVELOPMENT_TEAM = 2W7AC6T8T5; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -1237,6 +1261,13 @@ }; /* End XCConfigurationList section */ +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + /* Begin XCRemoteSwiftPackageReference section */ FEE084F62EC172460045228E /* XCRemoteSwiftPackageReference "sqlite-data" */ = { isa = XCRemoteSwiftPackageReference; @@ -1257,6 +1288,10 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; FEE084F72EC172460045228E /* SQLiteData */ = { isa = XCSwiftPackageProductDependency; package = FEE084F62EC172460045228E /* XCRemoteSwiftPackageReference "sqlite-data" */; @@ -1272,7 +1307,17 @@ package = FEE084F92EC1725A0045228E /* XCRemoteSwiftPackageReference "swift-http-structured-headers" */; productName = StructuredFieldValues; }; + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; /* End XCSwiftPackageProductDependency section */ +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } diff --git a/mobile/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/mobile/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 187a67cb27..a9381e541a 100644 --- a/mobile/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/mobile/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,4 @@ { - "originHash" : "9be33bfaa68721646604aefff3cabbdaf9a193da192aae024c265065671f6c49", "pins" : [ { "identity" : "combine-schedulers", @@ -19,6 +18,24 @@ "version" : "7.8.0" } }, + { + "identity" : "keychainaccess", + "kind" : "remoteSourceControl", + "location" : "https://github.com/kishikawakatsumi/KeychainAccess", + "state" : { + "revision" : "84e546727d66f1adc5439debad16270d0fdd04e7", + "version" : "4.2.2" + } + }, + { + "identity" : "maplibre-gl-native-distribution", + "kind" : "remoteSourceControl", + "location" : "https://github.com/maplibre/maplibre-gl-native-distribution.git", + "state" : { + "revision" : "60d9bb85c94ce6e7fc4406cd32529fd12bdb7809", + "version" : "6.14.0" + } + }, { "identity" : "sqlite-data", "kind" : "remoteSourceControl", @@ -146,5 +163,5 @@ } } ], - "version" : 3 + "version" : 2 } diff --git a/mobile/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/mobile/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 2367c52b97..f8dd8b0b9f 100644 --- a/mobile/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/mobile/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -5,6 +5,24 @@ + + + + + + + + + + ) -> Void) - func onAndroidUpload(completion: @escaping (Result) -> Void) + func onAndroidUpload(maxMinutes maxMinutesArg: Int64?, completion: @escaping (Result) -> Void) func cancel(completion: @escaping (Result) -> Void) } class BackgroundWorkerFlutterApi: BackgroundWorkerFlutterApiProtocol { @@ -379,10 +379,10 @@ class BackgroundWorkerFlutterApi: BackgroundWorkerFlutterApiProtocol { } } } - func onAndroidUpload(completion: @escaping (Result) -> Void) { + func onAndroidUpload(maxMinutes maxMinutesArg: Int64?, completion: @escaping (Result) -> Void) { let channelName: String = "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFlutterApi.onAndroidUpload\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec) - channel.sendMessage(nil) { response in + channel.sendMessage([maxMinutesArg] as [Any?]) { response in guard let listResponse = response as? [Any?] else { completion(.failure(createConnectionError(withChannelName: channelName))) return diff --git a/mobile/ios/Runner/Background/BackgroundWorker.swift b/mobile/ios/Runner/Background/BackgroundWorker.swift index c5b5e1778a..ad583065f0 100644 --- a/mobile/ios/Runner/Background/BackgroundWorker.swift +++ b/mobile/ios/Runner/Background/BackgroundWorker.swift @@ -121,8 +121,8 @@ class BackgroundWorker: BackgroundWorkerBgHostApi { /** * Cancels the currently running background task, either due to timeout or external request. - * Sends a cancel signal to the Flutter side and sets up a fallback timer to ensure - * the completion handler is eventually called even if Flutter doesn't respond. + * Only tears down the engine after Dart confirms it's drained. If Dart overruns iOS's grace window, + * the expiration handler still calls setTaskCompleted and iOS suspends us. */ func close() { if isComplete { @@ -132,12 +132,6 @@ class BackgroundWorker: BackgroundWorkerBgHostApi { flutterApi?.cancel { result in self.complete(success: false) } - - // Fallback safety mechanism: ensure completion is called within 2 seconds - // This prevents the background task from hanging indefinitely if Flutter doesn't respond - Timer.scheduledTimer(withTimeInterval: 2, repeats: false) { _ in - self.complete(success: false) - } } diff --git a/mobile/ios/Runner/Core/Network.g.swift b/mobile/ios/Runner/Core/Network.g.swift index 7d9b9f14be..265923d165 100644 --- a/mobile/ios/Runner/Core/Network.g.swift +++ b/mobile/ios/Runner/Core/Network.g.swift @@ -288,6 +288,7 @@ protocol NetworkApi { func hasCertificate() throws -> Bool func getClientPointer() throws -> Int64 func setRequestHeaders(headers: [String: String], serverUrls: [String], token: String?) throws + func getAppGroupId() throws -> String } /// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. @@ -388,5 +389,18 @@ class NetworkApiSetup { } else { setRequestHeadersChannel.setMessageHandler(nil) } + let getAppGroupIdChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NetworkApi.getAppGroupId\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + getAppGroupIdChannel.setMessageHandler { _, reply in + do { + let result = try api.getAppGroupId() + reply(wrapResult(result)) + } catch { + reply(wrapError(error)) + } + } + } else { + getAppGroupIdChannel.setMessageHandler(nil) + } } } diff --git a/mobile/ios/Runner/Core/NetworkApiImpl.swift b/mobile/ios/Runner/Core/NetworkApiImpl.swift index 82a913d837..288b8e9539 100644 --- a/mobile/ios/Runner/Core/NetworkApiImpl.swift +++ b/mobile/ios/Runner/Core/NetworkApiImpl.swift @@ -61,6 +61,10 @@ class NetworkApiImpl: NetworkApi { return Int64(Int(bitPattern: pointer)) } + func getAppGroupId() throws -> String { + return Bundle.main.object(forInfoDictionaryKey: "AppGroupId") as! String + } + func setRequestHeaders(headers: [String : String], serverUrls: [String], token: String?) throws { URLSessionManager.setServerUrls(serverUrls) diff --git a/mobile/ios/Runner/Core/URLSessionManager.swift b/mobile/ios/Runner/Core/URLSessionManager.swift index e9d65d3113..48963aa577 100644 --- a/mobile/ios/Runner/Core/URLSessionManager.swift +++ b/mobile/ios/Runner/Core/URLSessionManager.swift @@ -4,7 +4,7 @@ import native_video_player let CLIENT_CERT_LABEL = "app.alextran.immich.client_identity" let HEADERS_KEY = "immich.request_headers" let SERVER_URLS_KEY = "immich.server_urls" -let APP_GROUP = "group.app.immich.share" +let APP_GROUP = Bundle.main.object(forInfoDictionaryKey: "AppGroupId") as! String let COOKIE_EXPIRY_DAYS: TimeInterval = 400 enum AuthCookie: CaseIterable { diff --git a/mobile/ios/Runner/Info.plist b/mobile/ios/Runner/Info.plist index 3b030e4f86..0ca810438e 100644 --- a/mobile/ios/Runner/Info.plist +++ b/mobile/ios/Runner/Info.plist @@ -78,7 +78,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 2.7.5 + 3.0.0 CFBundleSignature ???? CFBundleURLTypes diff --git a/mobile/ios/Runner/Permission/PermissionApi.g.swift b/mobile/ios/Runner/Permission/PermissionApi.g.swift new file mode 100644 index 0000000000..53ad9e5b11 --- /dev/null +++ b/mobile/ios/Runner/Permission/PermissionApi.g.swift @@ -0,0 +1,106 @@ +// Autogenerated from Pigeon (v26.3.4), do not edit directly. +// See also: https://pub.dev/packages/pigeon + +import Foundation + +#if os(iOS) + import Flutter +#elseif os(macOS) + import FlutterMacOS +#else + #error("Unsupported platform.") +#endif + +private func wrapResult(_ result: Any?) -> [Any?] { + return [result] +} + +private func wrapError(_ error: Any) -> [Any?] { + if let pigeonError = error as? PigeonError { + return [ + pigeonError.code, + pigeonError.message, + pigeonError.details, + ] + } + if let flutterError = error as? FlutterError { + return [ + flutterError.code, + flutterError.message, + flutterError.details, + ] + } + return [ + "\(error)", + "\(Swift.type(of: error))", + "Stacktrace: \(Thread.callStackSymbols)", + ] +} + +private func isNullish(_ value: Any?) -> Bool { + return value is NSNull || value == nil +} + +private func nilOrValue(_ value: Any?) -> T? { + if value is NSNull { return nil } + return value as! T? +} + +/// Generated protocol from Pigeon that represents a handler of messages from Flutter. +protocol PermissionApi { + func hasManageMediaPermission() throws -> Bool + func requestManageMediaPermission(completion: @escaping (Result) -> Void) + func manageMediaPermission(completion: @escaping (Result) -> Void) +} + +/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. +class PermissionApiSetup { + static var codec: FlutterStandardMessageCodec { FlutterStandardMessageCodec.sharedInstance() } + /// Sets up an instance of `PermissionApi` to handle messages through the `binaryMessenger`. + static func setUp(binaryMessenger: FlutterBinaryMessenger, api: PermissionApi?, messageChannelSuffix: String = "") { + let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : "" + let hasManageMediaPermissionChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.PermissionApi.hasManageMediaPermission\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + hasManageMediaPermissionChannel.setMessageHandler { _, reply in + do { + let result = try api.hasManageMediaPermission() + reply(wrapResult(result)) + } catch { + reply(wrapError(error)) + } + } + } else { + hasManageMediaPermissionChannel.setMessageHandler(nil) + } + let requestManageMediaPermissionChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.PermissionApi.requestManageMediaPermission\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + requestManageMediaPermissionChannel.setMessageHandler { _, reply in + api.requestManageMediaPermission { result in + switch result { + case .success(let res): + reply(wrapResult(res)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + requestManageMediaPermissionChannel.setMessageHandler(nil) + } + let manageMediaPermissionChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.PermissionApi.manageMediaPermission\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + manageMediaPermissionChannel.setMessageHandler { _, reply in + api.manageMediaPermission { result in + switch result { + case .success(let res): + reply(wrapResult(res)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + manageMediaPermissionChannel.setMessageHandler(nil) + } + } +} diff --git a/mobile/ios/Runner/Permission/PermissionApiImpl.swift b/mobile/ios/Runner/Permission/PermissionApiImpl.swift new file mode 100644 index 0000000000..e725b742fd --- /dev/null +++ b/mobile/ios/Runner/Permission/PermissionApiImpl.swift @@ -0,0 +1,15 @@ +import Foundation + +class PermissionApiImpl: PermissionApi { + func hasManageMediaPermission() throws -> Bool { + return false + } + + func requestManageMediaPermission(completion: @escaping (Result) -> Void) { + completion(.success(false)) + } + + func manageMediaPermission(completion: @escaping (Result) -> Void) { + completion(.success(false)) + } +} diff --git a/mobile/ios/Runner/Runner.entitlements b/mobile/ios/Runner/Runner.entitlements index e5862cb213..c8a6be9cbb 100644 --- a/mobile/ios/Runner/Runner.entitlements +++ b/mobile/ios/Runner/Runner.entitlements @@ -10,7 +10,7 @@ com.apple.security.application-groups - group.app.immich.share + $(CUSTOM_GROUP_ID) diff --git a/mobile/ios/Runner/RunnerProfile.entitlements b/mobile/ios/Runner/RunnerProfile.entitlements index 6a5c086baf..93a4aab552 100644 --- a/mobile/ios/Runner/RunnerProfile.entitlements +++ b/mobile/ios/Runner/RunnerProfile.entitlements @@ -12,7 +12,7 @@ com.apple.security.application-groups - group.app.immich.share + $(CUSTOM_GROUP_ID) diff --git a/mobile/ios/Runner/Sync/Messages.g.swift b/mobile/ios/Runner/Sync/Messages.g.swift index 2933fc89af..a752785c5b 100644 --- a/mobile/ios/Runner/Sync/Messages.g.swift +++ b/mobile/ios/Runner/Sync/Messages.g.swift @@ -526,17 +526,19 @@ class MessagesPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable { /// Generated protocol from Pigeon that represents a handler of messages from Flutter. protocol NativeSyncApi { - func shouldFullSync() throws -> Bool - func getMediaChanges() throws -> SyncDelta + func shouldFullSync(completion: @escaping (Result) -> Void) + func getMediaChanges(completion: @escaping (Result) -> Void) func checkpointSync() throws func clearSyncCheckpoint() throws - func getAssetIdsForAlbum(albumId: String) throws -> [String] - func getAlbums() throws -> [PlatformAlbum] + func getAssetIdsForAlbum(albumId: String, completion: @escaping (Result<[String], Error>) -> Void) + func getAlbums(completion: @escaping (Result<[PlatformAlbum], Error>) -> Void) func getAssetsCountSince(albumId: String, timestamp: Int64) throws -> Int64 - func getAssetsForAlbum(albumId: String, updatedTimeCond: Int64?) throws -> [PlatformAsset] + func getAssetsForAlbum(albumId: String, updatedTimeCond: Int64?, completion: @escaping (Result<[PlatformAsset], Error>) -> Void) func hashAssets(assetIds: [String], allowNetworkAccess: Bool, completion: @escaping (Result<[HashResult], Error>) -> Void) func cancelHashing() throws + func cancelSync() throws func getTrashedAssets() throws -> [String: [PlatformAsset]] + func restoreFromTrashById(mediaId: String, type: Int64, completion: @escaping (Result) -> Void) func getCloudIdForAssetIds(assetIds: [String]) throws -> [CloudIdResult] } @@ -554,26 +556,28 @@ class NativeSyncApiSetup { let shouldFullSyncChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.shouldFullSync\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) if let api = api { shouldFullSyncChannel.setMessageHandler { _, reply in - do { - let result = try api.shouldFullSync() - reply(wrapResult(result)) - } catch { - reply(wrapError(error)) + api.shouldFullSync { result in + switch result { + case .success(let res): + reply(wrapResult(res)) + case .failure(let error): + reply(wrapError(error)) + } } } } else { shouldFullSyncChannel.setMessageHandler(nil) } - let getMediaChangesChannel = taskQueue == nil - ? FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getMediaChanges\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) - : FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getMediaChanges\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec, taskQueue: taskQueue) + let getMediaChangesChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getMediaChanges\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) if let api = api { getMediaChangesChannel.setMessageHandler { _, reply in - do { - let result = try api.getMediaChanges() - reply(wrapResult(result)) - } catch { - reply(wrapError(error)) + api.getMediaChanges { result in + switch result { + case .success(let res): + reply(wrapResult(res)) + case .failure(let error): + reply(wrapError(error)) + } } } } else { @@ -605,33 +609,33 @@ class NativeSyncApiSetup { } else { clearSyncCheckpointChannel.setMessageHandler(nil) } - let getAssetIdsForAlbumChannel = taskQueue == nil - ? FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAssetIdsForAlbum\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) - : FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAssetIdsForAlbum\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec, taskQueue: taskQueue) + let getAssetIdsForAlbumChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAssetIdsForAlbum\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) if let api = api { getAssetIdsForAlbumChannel.setMessageHandler { message, reply in let args = message as! [Any?] let albumIdArg = args[0] as! String - do { - let result = try api.getAssetIdsForAlbum(albumId: albumIdArg) - reply(wrapResult(result)) - } catch { - reply(wrapError(error)) + api.getAssetIdsForAlbum(albumId: albumIdArg) { result in + switch result { + case .success(let res): + reply(wrapResult(res)) + case .failure(let error): + reply(wrapError(error)) + } } } } else { getAssetIdsForAlbumChannel.setMessageHandler(nil) } - let getAlbumsChannel = taskQueue == nil - ? FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAlbums\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) - : FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAlbums\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec, taskQueue: taskQueue) + let getAlbumsChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAlbums\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) if let api = api { getAlbumsChannel.setMessageHandler { _, reply in - do { - let result = try api.getAlbums() - reply(wrapResult(result)) - } catch { - reply(wrapError(error)) + api.getAlbums { result in + switch result { + case .success(let res): + reply(wrapResult(res)) + case .failure(let error): + reply(wrapError(error)) + } } } } else { @@ -655,19 +659,19 @@ class NativeSyncApiSetup { } else { getAssetsCountSinceChannel.setMessageHandler(nil) } - let getAssetsForAlbumChannel = taskQueue == nil - ? FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAssetsForAlbum\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) - : FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAssetsForAlbum\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec, taskQueue: taskQueue) + let getAssetsForAlbumChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAssetsForAlbum\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) if let api = api { getAssetsForAlbumChannel.setMessageHandler { message, reply in let args = message as! [Any?] let albumIdArg = args[0] as! String let updatedTimeCondArg: Int64? = nilOrValue(args[1]) - do { - let result = try api.getAssetsForAlbum(albumId: albumIdArg, updatedTimeCond: updatedTimeCondArg) - reply(wrapResult(result)) - } catch { - reply(wrapError(error)) + api.getAssetsForAlbum(albumId: albumIdArg, updatedTimeCond: updatedTimeCondArg) { result in + switch result { + case .success(let res): + reply(wrapResult(res)) + case .failure(let error): + reply(wrapError(error)) + } } } } else { @@ -706,6 +710,19 @@ class NativeSyncApiSetup { } else { cancelHashingChannel.setMessageHandler(nil) } + let cancelSyncChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.cancelSync\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + cancelSyncChannel.setMessageHandler { _, reply in + do { + try api.cancelSync() + reply(wrapResult(nil)) + } catch { + reply(wrapError(error)) + } + } + } else { + cancelSyncChannel.setMessageHandler(nil) + } let getTrashedAssetsChannel = taskQueue == nil ? FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getTrashedAssets\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) : FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getTrashedAssets\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec, taskQueue: taskQueue) @@ -721,6 +738,24 @@ class NativeSyncApiSetup { } else { getTrashedAssetsChannel.setMessageHandler(nil) } + let restoreFromTrashByIdChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.restoreFromTrashById\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + restoreFromTrashByIdChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let mediaIdArg = args[0] as! String + let typeArg = args[1] as! Int64 + api.restoreFromTrashById(mediaId: mediaIdArg, type: typeArg) { result in + switch result { + case .success(let res): + reply(wrapResult(res)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + restoreFromTrashByIdChannel.setMessageHandler(nil) + } let getCloudIdForAssetIdsChannel = taskQueue == nil ? FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getCloudIdForAssetIds\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) : FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getCloudIdForAssetIds\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec, taskQueue: taskQueue) diff --git a/mobile/ios/Runner/Sync/MessagesImpl.swift b/mobile/ios/Runner/Sync/MessagesImpl.swift index ec96729d8f..ddfd023690 100644 --- a/mobile/ios/Runner/Sync/MessagesImpl.swift +++ b/mobile/ios/Runner/Sync/MessagesImpl.swift @@ -39,6 +39,9 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin { private static let hashCancelledCode = "HASH_CANCELLED" private static let hashCancelled = Result<[HashResult], Error>.failure(PigeonError(code: hashCancelledCode, message: "Hashing cancelled", details: nil)) + private var syncTask: Task? + private static let syncCancelledCode = "SYNC_CANCELLED" + private static let syncCancelled = PigeonError(code: syncCancelledCode, message: "Sync cancelled", details: nil) init(with defaults: UserDefaults = .standard) { self.defaults = defaults @@ -71,7 +74,11 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin { saveChangeToken(token: PHPhotoLibrary.shared().currentChangeToken) } - func shouldFullSync() -> Bool { + func shouldFullSync(completion: @escaping (Result) -> Void) { + runSync(completion) { $0.shouldFullSync() } + } + + private func shouldFullSync() -> Bool { guard #available(iOS 16, *), PHPhotoLibrary.authorizationStatus(for: .readWrite) == .authorized, let storedToken = getChangeToken() else { @@ -87,12 +94,17 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin { return false } - func getAlbums() throws -> [PlatformAlbum] { + func getAlbums(completion: @escaping (Result<[PlatformAlbum], Error>) -> Void) { + runSync(completion) { try $0.getAlbums() } + } + + private func getAlbums() throws -> [PlatformAlbum] { var albums: [PlatformAlbum] = [] - albumTypes.forEach { type in + for type in albumTypes { let collections = PHAssetCollection.fetchAssetCollections(with: type, subtype: .any, options: nil) for i in 0.. SyncDelta { + func getMediaChanges(completion: @escaping (Result) -> Void) { + runSync(completion) { try $0.getMediaChanges() } + } + + private func getMediaChanges() throws -> SyncDelta { guard #available(iOS 16, *) else { throw PigeonError(code: "UNSUPPORTED_OS", message: "This feature requires iOS 16 or later.", details: nil) } @@ -146,51 +162,49 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin { return SyncDelta(hasChanges: false, updates: [], deletes: [], assetAlbums: [:]) } - do { - let changes = try PHPhotoLibrary.shared().fetchPersistentChanges(since: storedToken) + let changes = try PHPhotoLibrary.shared().fetchPersistentChanges(since: storedToken) + + var updatedAssets: Set = [] + var deletedAssets: Set = [] + + for change in changes { + try Task.checkCancellation() + guard let details = try? change.changeDetails(for: PHObjectType.asset) else { continue } - var updatedAssets: Set = [] - var deletedAssets: Set = [] + let updated = details.updatedLocalIdentifiers.union(details.insertedLocalIdentifiers) + deletedAssets.formUnion(details.deletedLocalIdentifiers) - for change in changes { - guard let details = try? change.changeDetails(for: PHObjectType.asset) else { continue } + if (updated.isEmpty) { continue } + + let options = PHFetchOptions() + options.includeHiddenAssets = false + let result = PHAsset.fetchAssets(withLocalIdentifiers: Array(updated), options: options) + for i in 0..) -> [String: [String]] { guard !assets.isEmpty else { return [:] @@ -213,7 +227,11 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin { return albumAssets } - func getAssetIdsForAlbum(albumId: String) throws -> [String] { + func getAssetIdsForAlbum(albumId: String, completion: @escaping (Result<[String], Error>) -> Void) { + runSync(completion) { try $0.getAssetIdsForAlbum(albumId: albumId) } + } + + private func getAssetIdsForAlbum(albumId: String) throws -> [String] { let collections = PHAssetCollection.fetchAssetCollections(withLocalIdentifiers: [albumId], options: nil) guard let album = collections.firstObject else { return [] @@ -223,9 +241,14 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin { let options = PHFetchOptions() options.includeHiddenAssets = false let assets = getAssetsFromAlbum(in: album, options: options) - assets.enumerateObjects { (asset, _, _) in + assets.enumerateObjects { (asset, _, stop) in + if Task.isCancelled { + stop.pointee = true + return + } ids.append(asset.localIdentifier) } + try Task.checkCancellation() return ids } @@ -243,7 +266,11 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin { return Int64(assets.count) } - func getAssetsForAlbum(albumId: String, updatedTimeCond: Int64?) throws -> [PlatformAsset] { + func getAssetsForAlbum(albumId: String, updatedTimeCond: Int64?, completion: @escaping (Result<[PlatformAsset], Error>) -> Void) { + runSync(completion) { try $0.getAssetsForAlbum(albumId: albumId, updatedTimeCond: updatedTimeCond) } + } + + private func getAssetsForAlbum(albumId: String, updatedTimeCond: Int64?) throws -> [PlatformAsset] { let collections = PHAssetCollection.fetchAssetCollections(withLocalIdentifiers: [albumId], options: nil) guard let album = collections.firstObject else { return [] @@ -262,9 +289,14 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin { } var assets: [PlatformAsset] = [] - result.enumerateObjects { (asset, _, _) in + result.enumerateObjects { (asset, _, stop) in + if Task.isCancelled { + stop.pointee = true + return + } assets.append(asset.toPlatformAsset()) } + try Task.checkCancellation() return assets } @@ -324,6 +356,31 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin { hashTask = nil } + func cancelSync() { + syncTask?.cancel() + syncTask = nil + } + + private func runSync( + _ completion: @escaping (Result) -> Void, + _ work: @escaping (NativeSyncApiImpl) throws -> T + ) { + syncTask?.cancel() + syncTask = Task { [weak self] in + guard let self else { return nil } + let result: Result + do { + result = .success(try work(self)) + } catch is CancellationError { + result = .failure(Self.syncCancelled) + } catch { + result = .failure(error) + } + self.completeWhenActive(for: completion, with: result) + return nil + } + } + private func hashAsset(_ asset: PHAsset, allowNetworkAccess: Bool) async -> HashResult? { class RequestRef { var id: PHAssetResourceDataRequestID? @@ -382,6 +439,10 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin { func getTrashedAssets() throws -> [String: [PlatformAsset]] { throw PigeonError(code: "UNSUPPORTED_OS", message: "This feature not supported on iOS.", details: nil) } + + func restoreFromTrashById(mediaId: String, type: Int64, completion: @escaping (Result) -> Void) { + completion(.success(false)) + } private func getAssetsFromAlbum(in album: PHAssetCollection, options: PHFetchOptions) -> PHFetchResult { // Ensure to actually getting all assets for the Recents album diff --git a/mobile/ios/ShareExtension/ShareExtension.entitlements b/mobile/ios/ShareExtension/ShareExtension.entitlements index d16dcca065..1328658832 100644 --- a/mobile/ios/ShareExtension/ShareExtension.entitlements +++ b/mobile/ios/ShareExtension/ShareExtension.entitlements @@ -4,7 +4,7 @@ com.apple.security.application-groups - group.app.immich.share + $(CUSTOM_GROUP_ID) \ No newline at end of file diff --git a/mobile/ios/WidgetExtension/ImmichAPI.swift b/mobile/ios/WidgetExtension/ImmichAPI.swift index 6ae2d502f8..c5d7971aba 100644 --- a/mobile/ios/WidgetExtension/ImmichAPI.swift +++ b/mobile/ios/WidgetExtension/ImmichAPI.swift @@ -2,7 +2,7 @@ import Foundation import SwiftUI import WidgetKit -let IMMICH_SHARE_GROUP = "group.app.immich.share" +let IMMICH_SHARE_GROUP = Bundle.main.object(forInfoDictionaryKey: "AppGroupId") as! String enum WidgetError: Error, Codable { case noLogin diff --git a/mobile/ios/WidgetExtension/Info.plist b/mobile/ios/WidgetExtension/Info.plist index d4e598ee31..15b00baa20 100644 --- a/mobile/ios/WidgetExtension/Info.plist +++ b/mobile/ios/WidgetExtension/Info.plist @@ -2,6 +2,8 @@ + AppGroupId + $(CUSTOM_GROUP_ID) NSAppTransportSecurity NSAllowsArbitraryLoads diff --git a/mobile/ios/WidgetExtension/WidgetExtension.entitlements b/mobile/ios/WidgetExtension/WidgetExtension.entitlements index d16dcca065..1328658832 100644 --- a/mobile/ios/WidgetExtension/WidgetExtension.entitlements +++ b/mobile/ios/WidgetExtension/WidgetExtension.entitlements @@ -4,7 +4,7 @@ com.apple.security.application-groups - group.app.immich.share + $(CUSTOM_GROUP_ID) \ No newline at end of file diff --git a/mobile/ios/fastlane/Fastfile b/mobile/ios/fastlane/Fastfile index ff9fc4580f..17fa91786e 100644 --- a/mobile/ios/fastlane/Fastfile +++ b/mobile/ios/fastlane/Fastfile @@ -21,6 +21,7 @@ platform :ios do CODE_SIGN_IDENTITY = "Apple Distribution: FUTO Holdings, Inc. (#{TEAM_ID})" BASE_BUNDLE_ID = "app.alextran.immich" DEV_BUNDLE_ID = "tech.futo.immich.testflight" + DEV_GROUP_ID = "group.app.immich.share.testflight" # Helper method to get App Store Connect API key def get_api_key @@ -33,6 +34,13 @@ platform :ios do ) end + # Helper method to assemble xcargs with optional CUSTOM_GROUP_ID override + def build_xcargs(group_id: nil) + args = "-skipMacroValidation CODE_SIGN_IDENTITY='#{CODE_SIGN_IDENTITY}' CODE_SIGN_STYLE=Manual" + args += " CUSTOM_GROUP_ID='#{group_id}'" if group_id + args + end + # Helper method to get version from pubspec.yaml def get_version_from_pubspec require 'yaml' @@ -41,7 +49,7 @@ def get_version_from_pubspec pubspec = YAML.load_file(pubspec_path) version_string = pubspec['version'] - version_string ? version_string.split('+').first : nil + version_string ? version_string.split('+').first.split('-').first : nil end # Helper method to configure code signing for all targets @@ -89,7 +97,8 @@ end version_number: nil, profile_name_main:, profile_name_share:, - profile_name_widget: + profile_name_widget:, + group_id: nil ) app_identifier = base_bundle_id @@ -97,7 +106,7 @@ end if version_number increment_version_number(version_number: version_number) end - + # Increment build number increment_build_number( build_number: latest_testflight_build_number( @@ -106,14 +115,14 @@ end ) + 1, xcodeproj: "./Runner.xcodeproj" ) - + # Build the app build_app( scheme: "Runner", workspace: "Runner.xcworkspace", configuration: configuration, export_method: "app-store", - xcargs: "-skipMacroValidation CODE_SIGN_IDENTITY='#{CODE_SIGN_IDENTITY}' CODE_SIGN_STYLE=Manual", + xcargs: build_xcargs(group_id: group_id), export_options: { provisioningProfiles: { "#{app_identifier}" => profile_name_main, @@ -165,7 +174,8 @@ end distribute_external: false, profile_name_main: main_profile_name, profile_name_share: share_profile_name, - profile_name_widget: widget_profile_name + profile_name_widget: widget_profile_name, + group_id: DEV_GROUP_ID ) end @@ -274,7 +284,7 @@ end configuration: "Release", export_method: "app-store", skip_package_ipa: true, - xcargs: "-skipMacroValidation CODE_SIGN_IDENTITY='#{CODE_SIGN_IDENTITY}' CODE_SIGN_STYLE=Manual", + xcargs: build_xcargs(group_id: DEV_GROUP_ID), export_options: { provisioningProfiles: { DEV_BUNDLE_ID => main_profile_name, diff --git a/mobile/lib/constants/colors.dart b/mobile/lib/constants/colors.dart index e39480de32..655d2d9c09 100644 --- a/mobile/lib/constants/colors.dart +++ b/mobile/lib/constants/colors.dart @@ -2,9 +2,6 @@ import 'package:flutter/material.dart'; enum ImmichColorPreset { indigo, deepPurple, pink, red, orange, yellow, lime, green, cyan, slateGray } -const ImmichColorPreset defaultColorPreset = ImmichColorPreset.indigo; -const String defaultColorPresetName = "indigo"; - const Color immichBrandColorLight = Color(0xFF4150AF); const Color immichBrandColorDark = Color(0xFFACCBFA); const Color whiteOpacity75 = Color.fromRGBO(255, 255, 255, 0.75); diff --git a/mobile/lib/constants/constants.dart b/mobile/lib/constants/constants.dart index 1748a2a57d..b7fec86b55 100644 --- a/mobile/lib/constants/constants.dart +++ b/mobile/lib/constants/constants.dart @@ -23,6 +23,7 @@ const String kBackupLivePhotoGroup = 'backup_live_photo_group'; const String kDownloadGroupImage = 'group_image'; const String kDownloadGroupVideo = 'group_video'; const String kDownloadGroupLivePhoto = 'group_livephoto'; +const String kShareDownloadGroup = 'group_share'; // Timeline constants const int kTimelineNoneSegmentSize = 120; @@ -30,7 +31,6 @@ const int kTimelineAssetLoadBatchSize = 1024; const int kTimelineAssetLoadOppositeSize = 64; // Widget keys -const String appShareGroupId = "group.app.immich.share"; const String kWidgetAuthToken = "widget_auth_token"; const String kWidgetServerEndpoint = "widget_server_url"; const String kWidgetCustomHeaders = "widget_custom_headers"; diff --git a/mobile/lib/constants/enums.dart b/mobile/lib/constants/enums.dart index 877145c322..473bd52b03 100644 --- a/mobile/lib/constants/enums.dart +++ b/mobile/lib/constants/enums.dart @@ -18,3 +18,7 @@ enum CleanupStep { selectDate, scan, delete } enum AssetKeepType { none, photosOnly, videosOnly } enum AssetDateAggregation { start, end } + +enum SlideshowLook { contain, cover, blurredBackground } + +enum SlideshowDirection { forward, backward, shuffle } diff --git a/mobile/lib/domain/models/album/album.model.dart b/mobile/lib/domain/models/album/album.model.dart index ef67d729ec..63f4ed6be3 100644 --- a/mobile/lib/domain/models/album/album.model.dart +++ b/mobile/lib/domain/models/album/album.model.dart @@ -61,8 +61,12 @@ class RemoteAlbum { @override bool operator ==(Object other) { - if (other is! RemoteAlbum) return false; - if (identical(this, other)) return true; + if (other is! RemoteAlbum) { + return false; + } + if (identical(this, other)) { + return true; + } return id == other.id && name == other.name && ownerId == other.ownerId && diff --git a/mobile/lib/domain/models/album/local_album.model.dart b/mobile/lib/domain/models/album/local_album.model.dart index ea06118aa1..9e8521fa02 100644 --- a/mobile/lib/domain/models/album/local_album.model.dart +++ b/mobile/lib/domain/models/album/local_album.model.dart @@ -49,8 +49,12 @@ class LocalAlbum { @override bool operator ==(Object other) { - if (other is! LocalAlbum) return false; - if (identical(this, other)) return true; + if (other is! LocalAlbum) { + return false; + } + if (identical(this, other)) { + return true; + } return other.id == id && other.name == name && diff --git a/mobile/lib/domain/models/asset/base_asset.model.dart b/mobile/lib/domain/models/asset/base_asset.model.dart index 15f705c65b..418b124930 100644 --- a/mobile/lib/domain/models/asset/base_asset.model.dart +++ b/mobile/lib/domain/models/asset/base_asset.model.dart @@ -51,12 +51,18 @@ sealed class BaseAsset { bool get isAnimatedImage => playbackStyle == AssetPlaybackStyle.imageAnimated; AssetPlaybackStyle get playbackStyle { - if (isVideo) return AssetPlaybackStyle.video; - if (isMotionPhoto) return AssetPlaybackStyle.livePhoto; + if (isVideo) { + return AssetPlaybackStyle.video; + } + if (isMotionPhoto) { + return AssetPlaybackStyle.livePhoto; + } if (isImage && durationMs != null && durationMs! > 0) { return AssetPlaybackStyle.imageAnimated; } - if (isImage) return AssetPlaybackStyle.image; + if (isImage) { + return AssetPlaybackStyle.image; + } return AssetPlaybackStyle.unknown; } @@ -98,7 +104,9 @@ sealed class BaseAsset { @override bool operator ==(Object other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } if (other is BaseAsset) { return name == other.name && type == other.type && diff --git a/mobile/lib/domain/models/asset/local_asset.model.dart b/mobile/lib/domain/models/asset/local_asset.model.dart index 04aa6cd846..04f0ae6c8c 100644 --- a/mobile/lib/domain/models/asset/local_asset.model.dart +++ b/mobile/lib/domain/models/asset/local_asset.model.dart @@ -74,8 +74,12 @@ class LocalAsset extends BaseAsset { // Not checking for remoteId here @override bool operator ==(Object other) { - if (other is! LocalAsset) return false; - if (identical(this, other)) return true; + if (other is! LocalAsset) { + return false; + } + if (identical(this, other)) { + return true; + } return super == other && id == other.id && cloudId == other.cloudId && diff --git a/mobile/lib/domain/models/asset/remote_asset.model.dart b/mobile/lib/domain/models/asset/remote_asset.model.dart index 36dc6242e1..b370825fdd 100644 --- a/mobile/lib/domain/models/asset/remote_asset.model.dart +++ b/mobile/lib/domain/models/asset/remote_asset.model.dart @@ -10,6 +10,8 @@ class RemoteAsset extends BaseAsset { final AssetVisibility visibility; final String ownerId; final String? stackId; + final DateTime? uploadedAt; + final DateTime? deletedAt; const RemoteAsset({ required this.id, @@ -20,6 +22,7 @@ class RemoteAsset extends BaseAsset { required super.type, required super.createdAt, required super.updatedAt, + this.uploadedAt, super.width, super.height, super.durationMs, @@ -29,6 +32,7 @@ class RemoteAsset extends BaseAsset { super.livePhotoVideoId, this.stackId, required super.isEdited, + this.deletedAt, }) : localAssetId = localId; @override @@ -46,6 +50,8 @@ class RemoteAsset extends BaseAsset { @override bool get isEditable => isImage && !isMotionPhoto && !isAnimatedImage; + bool get isTrashed => deletedAt != null; + @override String toString() { return '''Asset { @@ -55,6 +61,7 @@ class RemoteAsset extends BaseAsset { type: $type, createdAt: $createdAt, updatedAt: $updatedAt, + uploadedAt: ${uploadedAt ?? ""}, width: ${width ?? ""}, height: ${height ?? ""}, durationMs: ${durationMs ?? ""}, @@ -71,14 +78,20 @@ class RemoteAsset extends BaseAsset { // Not checking for localId here @override bool operator ==(Object other) { - if (other is! RemoteAsset) return false; - if (identical(this, other)) return true; + if (other is! RemoteAsset) { + return false; + } + if (identical(this, other)) { + return true; + } return super == other && id == other.id && ownerId == other.ownerId && thumbHash == other.thumbHash && visibility == other.visibility && - stackId == other.stackId; + stackId == other.stackId && + uploadedAt == other.uploadedAt && + deletedAt == other.deletedAt; } @override @@ -89,7 +102,9 @@ class RemoteAsset extends BaseAsset { localId.hashCode ^ thumbHash.hashCode ^ visibility.hashCode ^ - stackId.hashCode; + stackId.hashCode ^ + uploadedAt.hashCode ^ + deletedAt.hashCode; RemoteAsset copyWith({ String? id, @@ -100,6 +115,7 @@ class RemoteAsset extends BaseAsset { AssetType? type, DateTime? createdAt, DateTime? updatedAt, + DateTime? uploadedAt, int? width, int? height, int? durationMs, @@ -109,6 +125,7 @@ class RemoteAsset extends BaseAsset { String? livePhotoVideoId, String? stackId, bool? isEdited, + DateTime? deletedAt, }) { return RemoteAsset( id: id ?? this.id, @@ -119,6 +136,7 @@ class RemoteAsset extends BaseAsset { type: type ?? this.type, createdAt: createdAt ?? this.createdAt, updatedAt: updatedAt ?? this.updatedAt, + uploadedAt: uploadedAt ?? this.uploadedAt, width: width ?? this.width, height: height ?? this.height, durationMs: durationMs ?? this.durationMs, @@ -128,6 +146,7 @@ class RemoteAsset extends BaseAsset { livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId, stackId: stackId ?? this.stackId, isEdited: isEdited ?? this.isEdited, + deletedAt: deletedAt ?? this.deletedAt, ); } } @@ -144,6 +163,8 @@ class RemoteAssetExif extends RemoteAsset { required super.type, required super.createdAt, required super.updatedAt, + super.uploadedAt, + super.deletedAt, super.width, super.height, super.durationMs, @@ -158,8 +179,12 @@ class RemoteAssetExif extends RemoteAsset { @override bool operator ==(Object other) { - if (other is! RemoteAssetExif) return false; - if (identical(this, other)) return true; + if (other is! RemoteAssetExif) { + return false; + } + if (identical(this, other)) { + return true; + } return super == other && exifInfo == other.exifInfo; } @@ -176,6 +201,8 @@ class RemoteAssetExif extends RemoteAsset { AssetType? type, DateTime? createdAt, DateTime? updatedAt, + DateTime? uploadedAt, + DateTime? deletedAt, int? width, int? height, int? durationMs, @@ -196,6 +223,8 @@ class RemoteAssetExif extends RemoteAsset { type: type ?? this.type, createdAt: createdAt ?? this.createdAt, updatedAt: updatedAt ?? this.updatedAt, + uploadedAt: uploadedAt ?? this.uploadedAt, + deletedAt: deletedAt ?? this.deletedAt, width: width ?? this.width, height: height ?? this.height, durationMs: durationMs ?? this.durationMs, diff --git a/mobile/lib/domain/models/asset_face.model.dart b/mobile/lib/domain/models/asset_face.model.dart index f432b923e3..1388836946 100644 --- a/mobile/lib/domain/models/asset_face.model.dart +++ b/mobile/lib/domain/models/asset_face.model.dart @@ -68,7 +68,9 @@ class AssetFace { @override bool operator ==(covariant AssetFace other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } return other.id == id && other.assetId == assetId && diff --git a/mobile/lib/domain/models/config/album_config.dart b/mobile/lib/domain/models/config/album_config.dart new file mode 100644 index 0000000000..a83fc32fbf --- /dev/null +++ b/mobile/lib/domain/models/config/album_config.dart @@ -0,0 +1,26 @@ +import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart'; + +class AlbumConfig { + final AlbumSortMode sortMode; + final bool isReverse; + final bool isGrid; + + const AlbumConfig({this.sortMode = AlbumSortMode.mostRecent, this.isReverse = true, this.isGrid = false}); + + AlbumConfig copyWith({AlbumSortMode? sortMode, bool? isReverse, bool? isGrid}) => AlbumConfig( + sortMode: sortMode ?? this.sortMode, + isReverse: isReverse ?? this.isReverse, + isGrid: isGrid ?? this.isGrid, + ); + + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AlbumConfig && other.sortMode == sortMode && other.isReverse == isReverse && other.isGrid == isGrid); + + @override + int get hashCode => Object.hash(sortMode, isReverse, isGrid); + + @override + String toString() => 'AlbumConfig(sortMode: $sortMode, isReverse: $isReverse, isGrid: $isGrid)'; +} diff --git a/mobile/lib/domain/models/config/app_config.dart b/mobile/lib/domain/models/config/app_config.dart new file mode 100644 index 0000000000..a955fb73a8 --- /dev/null +++ b/mobile/lib/domain/models/config/app_config.dart @@ -0,0 +1,196 @@ +import 'package:flutter/material.dart'; +import 'package:immich_mobile/constants/colors.dart'; +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/domain/models/config/album_config.dart'; +import 'package:immich_mobile/domain/models/config/backup_config.dart'; +import 'package:immich_mobile/domain/models/config/cleanup_config.dart'; +import 'package:immich_mobile/domain/models/config/image_config.dart'; +import 'package:immich_mobile/domain/models/config/map_config.dart'; +import 'package:immich_mobile/domain/models/config/network_config.dart'; +import 'package:immich_mobile/domain/models/config/slideshow_config.dart'; +import 'package:immich_mobile/domain/models/config/theme_config.dart'; +import 'package:immich_mobile/domain/models/config/timeline_config.dart'; +import 'package:immich_mobile/domain/models/config/viewer_config.dart'; +import 'package:immich_mobile/domain/models/log.model.dart'; +import 'package:immich_mobile/domain/models/settings_key.dart'; +import 'package:immich_mobile/domain/models/timeline.model.dart'; +import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart'; + +const defaultConfig = AppConfig(); + +class AppConfig { + final LogLevel logLevel; + final ThemeConfig theme; + final CleanupConfig cleanup; + final MapConfig map; + final TimelineConfig timeline; + final ImageConfig image; + final ViewerConfig viewer; + final SlideshowConfig slideshow; + final AlbumConfig album; + final BackupConfig backup; + final NetworkConfig network; + + const AppConfig({ + this.logLevel = .info, + this.theme = const .new(), + this.cleanup = const .new(), + this.map = const .new(), + this.timeline = const .new(), + this.image = const .new(), + this.viewer = const .new(), + this.slideshow = const .new(), + this.album = const .new(), + this.backup = const .new(), + this.network = const .new(), + }); + + AppConfig copyWith({ + LogLevel? logLevel, + ThemeConfig? theme, + CleanupConfig? cleanup, + MapConfig? map, + TimelineConfig? timeline, + ImageConfig? image, + ViewerConfig? viewer, + SlideshowConfig? slideshow, + AlbumConfig? album, + BackupConfig? backup, + NetworkConfig? network, + }) => .new( + logLevel: logLevel ?? this.logLevel, + theme: theme ?? this.theme, + cleanup: cleanup ?? this.cleanup, + map: map ?? this.map, + timeline: timeline ?? this.timeline, + image: image ?? this.image, + viewer: viewer ?? this.viewer, + slideshow: slideshow ?? this.slideshow, + album: album ?? this.album, + backup: backup ?? this.backup, + network: network ?? this.network, + ); + + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AppConfig && + other.logLevel == logLevel && + other.theme == theme && + other.cleanup == cleanup && + other.map == map && + other.timeline == timeline && + other.image == image && + other.viewer == viewer && + other.slideshow == slideshow && + other.album == album && + other.backup == backup && + other.network == network); + + @override + int get hashCode => + Object.hash(logLevel, theme, cleanup, map, timeline, image, viewer, slideshow, album, backup, network); + + @override + String toString() => + 'AppConfig(logLevel: $logLevel, theme: $theme, cleanup: $cleanup, map: $map, timeline: $timeline, image: $image, viewer: $viewer, slideshow: $slideshow, album: $album, backup: $backup, network: $network)'; + + T read(SettingsKey key) => + (switch (key) { + .logLevel => logLevel, + .themePrimaryColor => theme.primaryColor, + .themeMode => theme.mode, + .themeDynamic => theme.dynamicTheme, + .themeColorfulInterface => theme.colorfulInterface, + .imagePreferRemote => image.preferRemote, + .imageLoadOriginal => image.loadOriginal, + .viewerLoopVideo => viewer.loopVideo, + .viewerLoadOriginalVideo => viewer.loadOriginalVideo, + .viewerAutoPlayVideo => viewer.autoPlayVideo, + .viewerTapToNavigate => viewer.tapToNavigate, + .networkAutoEndpointSwitching => network.autoEndpointSwitching, + .networkPreferredWifiName => network.preferredWifiName, + .networkLocalEndpoint => network.localEndpoint, + .networkExternalEndpointList => network.externalEndpointList, + .networkCustomHeaders => network.customHeaders, + .albumSortMode => album.sortMode, + .albumIsReverse => album.isReverse, + .albumIsGrid => album.isGrid, + .backupEnabled => backup.enabled, + .backupUseCellularForVideos => backup.useCellularForVideos, + .backupUseCellularForPhotos => backup.useCellularForPhotos, + .backupRequireCharging => backup.requireCharging, + .backupTriggerDelay => backup.triggerDelay, + .backupSyncAlbums => backup.syncAlbums, + .timelineTilesPerRow => timeline.tilesPerRow, + .timelineGroupAssetsBy => timeline.groupAssetsBy, + .timelineStorageIndicator => timeline.storageIndicator, + .mapShowFavoriteOnly => map.favoritesOnly, + .mapRelativeDate => map.relativeDays, + .mapIncludeArchived => map.includeArchived, + .mapThemeMode => map.themeMode, + .mapWithPartners => map.withPartners, + .cleanupKeepFavorites => cleanup.keepFavorites, + .cleanupKeepMediaType => cleanup.keepMediaType, + .cleanupKeepAlbumIds => cleanup.keepAlbumIds, + .cleanupCutoffDaysAgo => cleanup.cutoffDaysAgo, + .cleanupDefaultsInitialized => cleanup.defaultsInitialized, + .slideshowTransition => slideshow.transition, + .slideshowRepeat => slideshow.repeat, + .slideshowDuration => slideshow.duration, + .slideshowLook => slideshow.look, + .slideshowDirection => slideshow.direction, + }) + as T; + + factory AppConfig.fromEntries(Map, Object> overrides) => + overrides.entries.fold(const AppConfig(), (config, entry) => config.write(entry.key, entry.value)); + + AppConfig write(SettingsKey key, T value) { + return switch (key) { + .logLevel => copyWith(logLevel: value as LogLevel), + .themePrimaryColor => copyWith(theme: theme.copyWith(primaryColor: value as ImmichColorPreset)), + .themeMode => copyWith(theme: theme.copyWith(mode: value as ThemeMode)), + .themeDynamic => copyWith(theme: theme.copyWith(dynamicTheme: value as bool)), + .themeColorfulInterface => copyWith(theme: theme.copyWith(colorfulInterface: value as bool)), + .imagePreferRemote => copyWith(image: image.copyWith(preferRemote: value as bool)), + .imageLoadOriginal => copyWith(image: image.copyWith(loadOriginal: value as bool)), + .viewerLoopVideo => copyWith(viewer: viewer.copyWith(loopVideo: value as bool)), + .viewerLoadOriginalVideo => copyWith(viewer: viewer.copyWith(loadOriginalVideo: value as bool)), + .viewerAutoPlayVideo => copyWith(viewer: viewer.copyWith(autoPlayVideo: value as bool)), + .viewerTapToNavigate => copyWith(viewer: viewer.copyWith(tapToNavigate: value as bool)), + .networkAutoEndpointSwitching => copyWith(network: network.copyWith(autoEndpointSwitching: value as bool)), + .networkPreferredWifiName => copyWith(network: network.copyWith(preferredWifiName: (value as String))), + .networkLocalEndpoint => copyWith(network: network.copyWith(localEndpoint: (value as String))), + .networkExternalEndpointList => copyWith(network: network.copyWith(externalEndpointList: value as List)), + .networkCustomHeaders => copyWith(network: network.copyWith(customHeaders: value as Map)), + .albumSortMode => copyWith(album: album.copyWith(sortMode: value as AlbumSortMode)), + .albumIsReverse => copyWith(album: album.copyWith(isReverse: value as bool)), + .albumIsGrid => copyWith(album: album.copyWith(isGrid: value as bool)), + .backupEnabled => copyWith(backup: backup.copyWith(enabled: value as bool)), + .backupUseCellularForVideos => copyWith(backup: backup.copyWith(useCellularForVideos: value as bool)), + .backupUseCellularForPhotos => copyWith(backup: backup.copyWith(useCellularForPhotos: value as bool)), + .backupRequireCharging => copyWith(backup: backup.copyWith(requireCharging: value as bool)), + .backupTriggerDelay => copyWith(backup: backup.copyWith(triggerDelay: value as int)), + .backupSyncAlbums => copyWith(backup: backup.copyWith(syncAlbums: value as bool)), + .timelineTilesPerRow => copyWith(timeline: timeline.copyWith(tilesPerRow: value as int)), + .timelineGroupAssetsBy => copyWith(timeline: timeline.copyWith(groupAssetsBy: value as GroupAssetsBy)), + .timelineStorageIndicator => copyWith(timeline: timeline.copyWith(storageIndicator: value as bool)), + .mapShowFavoriteOnly => copyWith(map: map.copyWith(favoritesOnly: value as bool)), + .mapRelativeDate => copyWith(map: map.copyWith(relativeDays: value as int)), + .mapIncludeArchived => copyWith(map: map.copyWith(includeArchived: value as bool)), + .mapThemeMode => copyWith(map: map.copyWith(themeMode: value as ThemeMode)), + .mapWithPartners => copyWith(map: map.copyWith(withPartners: value as bool)), + .cleanupKeepFavorites => copyWith(cleanup: cleanup.copyWith(keepFavorites: value as bool)), + .cleanupKeepMediaType => copyWith(cleanup: cleanup.copyWith(keepMediaType: value as AssetKeepType)), + .cleanupKeepAlbumIds => copyWith(cleanup: cleanup.copyWith(keepAlbumIds: value as List)), + .cleanupCutoffDaysAgo => copyWith(cleanup: cleanup.copyWith(cutoffDaysAgo: value as int)), + .cleanupDefaultsInitialized => copyWith(cleanup: cleanup.copyWith(defaultsInitialized: value as bool)), + .slideshowTransition => copyWith(slideshow: slideshow.copyWith(transition: value as bool)), + .slideshowRepeat => copyWith(slideshow: slideshow.copyWith(repeat: value as bool)), + .slideshowDuration => copyWith(slideshow: slideshow.copyWith(duration: value as int)), + .slideshowLook => copyWith(slideshow: slideshow.copyWith(look: value as SlideshowLook)), + .slideshowDirection => copyWith(slideshow: slideshow.copyWith(direction: value as SlideshowDirection)), + }; + } +} diff --git a/mobile/lib/domain/models/config/backup_config.dart b/mobile/lib/domain/models/config/backup_config.dart new file mode 100644 index 0000000000..19f91a4ed7 --- /dev/null +++ b/mobile/lib/domain/models/config/backup_config.dart @@ -0,0 +1,52 @@ +class BackupConfig { + final bool enabled; + final bool useCellularForVideos; + final bool useCellularForPhotos; + final bool requireCharging; + final int triggerDelay; + final bool syncAlbums; + + const BackupConfig({ + this.enabled = false, + this.useCellularForVideos = false, + this.useCellularForPhotos = false, + this.requireCharging = false, + this.triggerDelay = 30, + this.syncAlbums = false, + }); + + BackupConfig copyWith({ + bool? enabled, + bool? useCellularForVideos, + bool? useCellularForPhotos, + bool? requireCharging, + int? triggerDelay, + bool? syncAlbums, + }) => BackupConfig( + enabled: enabled ?? this.enabled, + useCellularForVideos: useCellularForVideos ?? this.useCellularForVideos, + useCellularForPhotos: useCellularForPhotos ?? this.useCellularForPhotos, + requireCharging: requireCharging ?? this.requireCharging, + triggerDelay: triggerDelay ?? this.triggerDelay, + syncAlbums: syncAlbums ?? this.syncAlbums, + ); + + @override + bool operator ==(Object other) => + identical(this, other) || + (other is BackupConfig && + other.enabled == enabled && + other.useCellularForVideos == useCellularForVideos && + other.useCellularForPhotos == useCellularForPhotos && + other.requireCharging == requireCharging && + other.triggerDelay == triggerDelay && + other.syncAlbums == syncAlbums); + + @override + int get hashCode => + Object.hash(enabled, useCellularForVideos, useCellularForPhotos, requireCharging, triggerDelay, syncAlbums); + + @override + String toString() => + 'BackupConfig(enabled: $enabled, useCellularForVideos: $useCellularForVideos, useCellularForPhotos: $useCellularForPhotos, requireCharging: $requireCharging, triggerDelay: $triggerDelay, syncAlbums: $syncAlbums)'; +} diff --git a/mobile/lib/domain/models/config/cleanup_config.dart b/mobile/lib/domain/models/config/cleanup_config.dart new file mode 100644 index 0000000000..4b34814492 --- /dev/null +++ b/mobile/lib/domain/models/config/cleanup_config.dart @@ -0,0 +1,48 @@ +import 'package:immich_mobile/constants/enums.dart'; + +class CleanupConfig { + final bool keepFavorites; + final AssetKeepType keepMediaType; + final List keepAlbumIds; + final int cutoffDaysAgo; + final bool defaultsInitialized; + + const CleanupConfig({ + this.keepFavorites = true, + this.keepMediaType = AssetKeepType.none, + this.keepAlbumIds = const [], + this.cutoffDaysAgo = -1, + this.defaultsInitialized = false, + }); + + CleanupConfig copyWith({ + bool? keepFavorites, + AssetKeepType? keepMediaType, + List? keepAlbumIds, + int? cutoffDaysAgo, + bool? defaultsInitialized, + }) => .new( + keepFavorites: keepFavorites ?? this.keepFavorites, + keepMediaType: keepMediaType ?? this.keepMediaType, + keepAlbumIds: keepAlbumIds ?? this.keepAlbumIds, + cutoffDaysAgo: cutoffDaysAgo ?? this.cutoffDaysAgo, + defaultsInitialized: defaultsInitialized ?? this.defaultsInitialized, + ); + + @override + bool operator ==(Object other) => + identical(this, other) || + (other is CleanupConfig && + other.keepFavorites == keepFavorites && + other.keepMediaType == keepMediaType && + other.keepAlbumIds == keepAlbumIds && + other.cutoffDaysAgo == cutoffDaysAgo && + other.defaultsInitialized == defaultsInitialized); + + @override + int get hashCode => Object.hash(keepFavorites, keepMediaType, keepAlbumIds, cutoffDaysAgo, defaultsInitialized); + + @override + String toString() => + 'CleanupConfig(keepFavorites: $keepFavorites, keepMediaType: $keepMediaType, keepAlbumIds: $keepAlbumIds, cutoffDaysAgo: $cutoffDaysAgo, defaultsInitialized: $defaultsInitialized)'; +} diff --git a/mobile/lib/domain/models/config/image_config.dart b/mobile/lib/domain/models/config/image_config.dart new file mode 100644 index 0000000000..8410a9010b --- /dev/null +++ b/mobile/lib/domain/models/config/image_config.dart @@ -0,0 +1,20 @@ +class ImageConfig { + final bool preferRemote; + final bool loadOriginal; + + const ImageConfig({this.preferRemote = false, this.loadOriginal = false}); + + ImageConfig copyWith({bool? preferRemote, bool? loadOriginal}) => + ImageConfig(preferRemote: preferRemote ?? this.preferRemote, loadOriginal: loadOriginal ?? this.loadOriginal); + + @override + bool operator ==(Object other) => + identical(this, other) || + (other is ImageConfig && other.preferRemote == preferRemote && other.loadOriginal == loadOriginal); + + @override + int get hashCode => Object.hash(preferRemote, loadOriginal); + + @override + String toString() => 'ImageConfig(preferRemoteImage: $preferRemote, loadOriginal: $loadOriginal)'; +} diff --git a/mobile/lib/domain/models/config/map_config.dart b/mobile/lib/domain/models/config/map_config.dart new file mode 100644 index 0000000000..e37ab0f431 --- /dev/null +++ b/mobile/lib/domain/models/config/map_config.dart @@ -0,0 +1,48 @@ +import 'package:flutter/material.dart'; + +class MapConfig { + final int relativeDays; + final bool favoritesOnly; + final bool includeArchived; + final ThemeMode themeMode; + final bool withPartners; + + const MapConfig({ + this.relativeDays = 0, + this.favoritesOnly = false, + this.includeArchived = false, + this.themeMode = ThemeMode.system, + this.withPartners = false, + }); + + MapConfig copyWith({ + int? relativeDays, + bool? favoritesOnly, + bool? includeArchived, + ThemeMode? themeMode, + bool? withPartners, + }) => MapConfig( + relativeDays: relativeDays ?? this.relativeDays, + favoritesOnly: favoritesOnly ?? this.favoritesOnly, + includeArchived: includeArchived ?? this.includeArchived, + themeMode: themeMode ?? this.themeMode, + withPartners: withPartners ?? this.withPartners, + ); + + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MapConfig && + other.relativeDays == relativeDays && + other.favoritesOnly == favoritesOnly && + other.includeArchived == includeArchived && + other.themeMode == themeMode && + other.withPartners == withPartners); + + @override + int get hashCode => Object.hash(relativeDays, favoritesOnly, includeArchived, themeMode, withPartners); + + @override + String toString() => + 'MapConfig(relativeDays: $relativeDays, favoritesOnly: $favoritesOnly, includeArchived: $includeArchived, themeMode: $themeMode, withPartners: $withPartners)'; +} diff --git a/mobile/lib/domain/models/config/network_config.dart b/mobile/lib/domain/models/config/network_config.dart new file mode 100644 index 0000000000..34666d60e1 --- /dev/null +++ b/mobile/lib/domain/models/config/network_config.dart @@ -0,0 +1,54 @@ +import 'package:flutter/foundation.dart'; + +class NetworkConfig { + final bool autoEndpointSwitching; + final String preferredWifiName; + final String localEndpoint; + final List externalEndpointList; + final Map customHeaders; + + const NetworkConfig({ + this.autoEndpointSwitching = false, + this.preferredWifiName = '', + this.localEndpoint = '', + this.externalEndpointList = const [], + this.customHeaders = const {}, + }); + + NetworkConfig copyWith({ + bool? autoEndpointSwitching, + String? preferredWifiName, + String? localEndpoint, + List? externalEndpointList, + Map? customHeaders, + }) => NetworkConfig( + autoEndpointSwitching: autoEndpointSwitching ?? this.autoEndpointSwitching, + preferredWifiName: preferredWifiName ?? this.preferredWifiName, + localEndpoint: localEndpoint ?? this.localEndpoint, + externalEndpointList: externalEndpointList ?? this.externalEndpointList, + customHeaders: customHeaders ?? this.customHeaders, + ); + + @override + bool operator ==(Object other) => + identical(this, other) || + (other is NetworkConfig && + other.autoEndpointSwitching == autoEndpointSwitching && + other.preferredWifiName == preferredWifiName && + other.localEndpoint == localEndpoint && + listEquals(other.externalEndpointList, externalEndpointList) && + mapEquals(other.customHeaders, customHeaders)); + + @override + int get hashCode => Object.hash( + autoEndpointSwitching, + preferredWifiName, + localEndpoint, + Object.hashAll(externalEndpointList), + Object.hashAllUnordered(customHeaders.entries.map((e) => Object.hash(e.key, e.value))), + ); + + @override + String toString() => + 'NetworkConfig(autoEndpointSwitching: $autoEndpointSwitching, preferredWifiName: $preferredWifiName, localEndpoint: $localEndpoint, externalEndpointList: $externalEndpointList, customHeaders: $customHeaders)'; +} diff --git a/mobile/lib/domain/models/config/slideshow_config.dart b/mobile/lib/domain/models/config/slideshow_config.dart new file mode 100644 index 0000000000..74c0ac9d38 --- /dev/null +++ b/mobile/lib/domain/models/config/slideshow_config.dart @@ -0,0 +1,48 @@ +import 'package:immich_mobile/constants/enums.dart'; + +class SlideshowConfig { + final bool transition; + final bool repeat; + final int duration; + final SlideshowLook look; + final SlideshowDirection direction; + + const SlideshowConfig({ + this.transition = true, + this.repeat = true, + this.duration = 5, + this.look = SlideshowLook.contain, + this.direction = SlideshowDirection.forward, + }); + + SlideshowConfig copyWith({ + bool? transition, + bool? repeat, + int? duration, + SlideshowLook? look, + SlideshowDirection? direction, + }) => SlideshowConfig( + transition: transition ?? this.transition, + repeat: repeat ?? this.repeat, + duration: duration ?? this.duration, + look: look ?? this.look, + direction: direction ?? this.direction, + ); + + @override + bool operator ==(Object other) => + identical(this, other) || + (other is SlideshowConfig && + other.transition == transition && + other.repeat == repeat && + other.duration == duration && + other.look == look && + other.direction == direction); + + @override + int get hashCode => Object.hash(transition, repeat, duration, look, direction); + + @override + String toString() => + 'SlideshowConfig(transition: $transition, repeat: $repeat, duration: $duration, look: $look, direction: $direction)'; +} diff --git a/mobile/lib/domain/models/config/theme_config.dart b/mobile/lib/domain/models/config/theme_config.dart new file mode 100644 index 0000000000..fa955c5d46 --- /dev/null +++ b/mobile/lib/domain/models/config/theme_config.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; +import 'package:immich_mobile/constants/colors.dart'; + +class ThemeConfig { + final ThemeMode mode; + final ImmichColorPreset primaryColor; + final bool dynamicTheme; + final bool colorfulInterface; + + const ThemeConfig({ + this.mode = .system, + this.primaryColor = .indigo, + this.dynamicTheme = false, + this.colorfulInterface = true, + }); + + ThemeConfig copyWith({ + ThemeMode? mode, + ImmichColorPreset? primaryColor, + bool? dynamicTheme, + bool? colorfulInterface, + }) => .new( + mode: mode ?? this.mode, + primaryColor: primaryColor ?? this.primaryColor, + dynamicTheme: dynamicTheme ?? this.dynamicTheme, + colorfulInterface: colorfulInterface ?? this.colorfulInterface, + ); + + @override + bool operator ==(Object other) => + identical(this, other) || + (other is ThemeConfig && + other.mode == mode && + other.primaryColor == primaryColor && + other.dynamicTheme == dynamicTheme && + other.colorfulInterface == colorfulInterface); + + @override + int get hashCode => Object.hash(mode, primaryColor, dynamicTheme, colorfulInterface); + + @override + String toString() => + 'ThemeConfig(mode: $mode, primaryColor: $primaryColor, dynamicTheme: $dynamicTheme, colorfulInterface: $colorfulInterface)'; +} diff --git a/mobile/lib/domain/models/config/timeline_config.dart b/mobile/lib/domain/models/config/timeline_config.dart new file mode 100644 index 0000000000..4b6b9d5625 --- /dev/null +++ b/mobile/lib/domain/models/config/timeline_config.dart @@ -0,0 +1,30 @@ +import 'package:immich_mobile/domain/models/timeline.model.dart'; + +class TimelineConfig { + final int tilesPerRow; + final GroupAssetsBy groupAssetsBy; + final bool storageIndicator; + + const TimelineConfig({this.tilesPerRow = 4, this.groupAssetsBy = GroupAssetsBy.day, this.storageIndicator = true}); + + TimelineConfig copyWith({int? tilesPerRow, GroupAssetsBy? groupAssetsBy, bool? storageIndicator}) => TimelineConfig( + tilesPerRow: tilesPerRow ?? this.tilesPerRow, + groupAssetsBy: groupAssetsBy ?? this.groupAssetsBy, + storageIndicator: storageIndicator ?? this.storageIndicator, + ); + + @override + bool operator ==(Object other) => + identical(this, other) || + (other is TimelineConfig && + other.tilesPerRow == tilesPerRow && + other.groupAssetsBy == groupAssetsBy && + other.storageIndicator == storageIndicator); + + @override + int get hashCode => Object.hash(tilesPerRow, groupAssetsBy, storageIndicator); + + @override + String toString() => + 'TimelineConfig(tilesPerRow: $tilesPerRow, groupAssetsBy: $groupAssetsBy, storageIndicator: $storageIndicator)'; +} diff --git a/mobile/lib/domain/models/config/viewer_config.dart b/mobile/lib/domain/models/config/viewer_config.dart new file mode 100644 index 0000000000..595f2bee5d --- /dev/null +++ b/mobile/lib/domain/models/config/viewer_config.dart @@ -0,0 +1,37 @@ +class ViewerConfig { + final bool loopVideo; + final bool loadOriginalVideo; + final bool autoPlayVideo; + final bool tapToNavigate; + + const ViewerConfig({ + this.loopVideo = true, + this.loadOriginalVideo = false, + this.autoPlayVideo = true, + this.tapToNavigate = false, + }); + + ViewerConfig copyWith({bool? loopVideo, bool? loadOriginalVideo, bool? autoPlayVideo, bool? tapToNavigate}) => + ViewerConfig( + loopVideo: loopVideo ?? this.loopVideo, + loadOriginalVideo: loadOriginalVideo ?? this.loadOriginalVideo, + autoPlayVideo: autoPlayVideo ?? this.autoPlayVideo, + tapToNavigate: tapToNavigate ?? this.tapToNavigate, + ); + + @override + bool operator ==(Object other) => + identical(this, other) || + (other is ViewerConfig && + other.loopVideo == loopVideo && + other.loadOriginalVideo == loadOriginalVideo && + other.autoPlayVideo == autoPlayVideo && + other.tapToNavigate == tapToNavigate); + + @override + int get hashCode => Object.hash(loopVideo, loadOriginalVideo, autoPlayVideo, tapToNavigate); + + @override + String toString() => + 'ViewerConfig(loopVideo: $loopVideo, loadOriginalVideo: $loadOriginalVideo, autoPlayVideo: $autoPlayVideo, tapToNavigate: $tapToNavigate)'; +} diff --git a/mobile/lib/domain/models/exif.model.dart b/mobile/lib/domain/models/exif.model.dart index 97c0ba3823..4284aef2ab 100644 --- a/mobile/lib/domain/models/exif.model.dart +++ b/mobile/lib/domain/models/exif.model.dart @@ -69,7 +69,9 @@ class ExifInfo { @override bool operator ==(covariant ExifInfo other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } return other.fileSize == fileSize && other.description == description && diff --git a/mobile/lib/domain/models/log.model.dart b/mobile/lib/domain/models/log.model.dart index 9902ca04ca..bed1729f9d 100644 --- a/mobile/lib/domain/models/log.model.dart +++ b/mobile/lib/domain/models/log.model.dart @@ -20,7 +20,9 @@ class LogMessage { @override bool operator ==(covariant LogMessage other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } return other.message == message && other.level == level && diff --git a/mobile/lib/domain/models/map.model.dart b/mobile/lib/domain/models/map.model.dart index ce0834f0cb..b55f176bfd 100644 --- a/mobile/lib/domain/models/map.model.dart +++ b/mobile/lib/domain/models/map.model.dart @@ -8,7 +8,9 @@ class Marker { @override bool operator ==(covariant Marker other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } return other.location == location && other.assetId == assetId; } diff --git a/mobile/lib/domain/models/memory.model.dart b/mobile/lib/domain/models/memory.model.dart index 40117c5ac6..e786ca18b1 100644 --- a/mobile/lib/domain/models/memory.model.dart +++ b/mobile/lib/domain/models/memory.model.dart @@ -2,7 +2,6 @@ import 'dart:convert'; import 'package:collection/collection.dart'; - import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; enum MemoryTypeEnum { @@ -36,7 +35,9 @@ class MemoryData { @override bool operator ==(covariant MemoryData other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } return other.year == year; } @@ -132,7 +133,9 @@ class DriftMemory { @override bool operator ==(covariant DriftMemory other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } final listEquals = const DeepCollectionEquality().equals; return other.id == id && diff --git a/mobile/lib/domain/models/ocr.model.dart b/mobile/lib/domain/models/ocr.model.dart index 648dcafd4c..403c06d44e 100644 --- a/mobile/lib/domain/models/ocr.model.dart +++ b/mobile/lib/domain/models/ocr.model.dart @@ -87,7 +87,9 @@ class Ocr { @override bool operator ==(Object other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } return other is Ocr && other.id == id && diff --git a/mobile/lib/domain/models/person.model.dart b/mobile/lib/domain/models/person.model.dart index 7559720c45..c7cdcff3af 100644 --- a/mobile/lib/domain/models/person.model.dart +++ b/mobile/lib/domain/models/person.model.dart @@ -69,7 +69,9 @@ class PersonDto { @override bool operator ==(covariant PersonDto other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } return other.id == id && other.birthDate == birthDate && @@ -160,7 +162,9 @@ class DriftPerson { @override bool operator ==(covariant DriftPerson other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } return other.id == id && other.createdAt == createdAt && diff --git a/mobile/lib/domain/models/search_result.model.dart b/mobile/lib/domain/models/search_result.model.dart index 21134b73d8..6a782e2f37 100644 --- a/mobile/lib/domain/models/search_result.model.dart +++ b/mobile/lib/domain/models/search_result.model.dart @@ -12,7 +12,9 @@ class SearchResult { @override bool operator ==(covariant SearchResult other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } final listEquals = const DeepCollectionEquality().equals; return listEquals(other.assets, assets) && other.nextPage == nextPage; diff --git a/mobile/lib/domain/models/setting.model.dart b/mobile/lib/domain/models/setting.model.dart index 2c46507331..d6d9e2902b 100644 --- a/mobile/lib/domain/models/setting.model.dart +++ b/mobile/lib/domain/models/setting.model.dart @@ -1,15 +1,7 @@ import 'package:immich_mobile/domain/models/store.model.dart'; enum Setting { - tilesPerRow(StoreKey.tilesPerRow, 4), - groupAssetsBy(StoreKey.groupAssetsBy, 0), - showStorageIndicator(StoreKey.storageIndicator, true), - loadOriginal(StoreKey.loadOriginal, false), - loadOriginalVideo(StoreKey.loadOriginalVideo, false), - autoPlayVideo(StoreKey.autoPlayVideo, true), - preferRemoteImage(StoreKey.preferRemoteImage, false), - advancedTroubleshooting(StoreKey.advancedTroubleshooting, false), - enableBackup(StoreKey.enableBackup, false); + advancedTroubleshooting(StoreKey.advancedTroubleshooting, false); const Setting(this.storeKey, this.defaultValue); diff --git a/mobile/lib/domain/models/settings_key.dart b/mobile/lib/domain/models/settings_key.dart new file mode 100644 index 0000000000..5277a37a60 --- /dev/null +++ b/mobile/lib/domain/models/settings_key.dart @@ -0,0 +1,217 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:immich_mobile/constants/colors.dart'; +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/domain/models/log.model.dart'; +import 'package:immich_mobile/domain/models/timeline.model.dart'; +import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart'; + +enum SettingsKey { + // Theme + themePrimaryColor(codec: _EnumCodec(ImmichColorPreset.values)), + themeMode(codec: _EnumCodec(ThemeMode.values)), + themeDynamic(), + themeColorfulInterface(), + + // Image + imagePreferRemote(), + imageLoadOriginal(), + + // Viewer + viewerLoopVideo(), + viewerLoadOriginalVideo(), + viewerAutoPlayVideo(), + viewerTapToNavigate(), + + // Network + networkAutoEndpointSwitching(), + networkPreferredWifiName(), + networkLocalEndpoint(), + networkExternalEndpointList>(codec: _ListCodec(_PrimitiveCodec.string)), + networkCustomHeaders>(codec: _MapCodec(_PrimitiveCodec.string, _PrimitiveCodec.string)), + + // Album + albumSortMode(codec: _EnumCodec(AlbumSortMode.values)), + albumIsReverse(), + albumIsGrid(), + + // Backup + backupEnabled(), + backupUseCellularForVideos(), + backupUseCellularForPhotos(), + backupRequireCharging(), + backupTriggerDelay(), + backupSyncAlbums(), + + // Timeline + timelineTilesPerRow(), + timelineGroupAssetsBy(codec: _EnumCodec(GroupAssetsBy.values)), + timelineStorageIndicator(), + + // Log + logLevel(codec: _EnumCodec(LogLevel.values)), + + // Map + mapShowFavoriteOnly(), + mapRelativeDate(), + mapIncludeArchived(), + mapThemeMode(codec: _EnumCodec(ThemeMode.values)), + mapWithPartners(), + + // Cleanup + cleanupKeepFavorites(), + cleanupKeepMediaType(codec: _EnumCodec(AssetKeepType.values)), + cleanupKeepAlbumIds>(codec: _ListCodec(_PrimitiveCodec.string)), + cleanupCutoffDaysAgo(), + cleanupDefaultsInitialized(), + + // Slideshow + slideshowTransition(), + slideshowRepeat(), + slideshowDuration(), + slideshowLook(codec: _EnumCodec(SlideshowLook.values)), + slideshowDirection(codec: _EnumCodec(SlideshowDirection.values)); + + final _SettingsCodec? _codecOverride; + + const SettingsKey({_SettingsCodec? codec}) : _codecOverride = codec; + + _SettingsCodec get _codec => _codecOverride ?? _SettingsCodec.forType(T); + + String encode(T value) => _codec.encode(value); + + T decode(String raw) => _codec.decode(raw); +} + +sealed class _SettingsCodec { + const _SettingsCodec(); + + String encode(T value); + T decode(String raw); + + static const Map> _primitives = { + int: _PrimitiveCodec.integer, + double: _PrimitiveCodec.real, + bool: _PrimitiveCodec.boolean, + String: _PrimitiveCodec.string, + DateTime: _DateTimeCodec(), + }; + + static _SettingsCodec forType(Type runtimeType) { + final codec = _primitives[runtimeType]; + if (codec == null) { + throw StateError('No primitive codec for $runtimeType. Provide an explicit codec when defining the SettingsKey.'); + } + return codec as _SettingsCodec; + } +} + +final class _EnumCodec extends _SettingsCodec { + final List values; + + const _EnumCodec(this.values); + + @override + String encode(T value) => value.name; + + @override + T decode(String raw) => values.firstWhere((v) => v.name == raw); +} + +final class _DateTimeCodec extends _SettingsCodec { + const _DateTimeCodec(); + + @override + String encode(DateTime value) => value.toIso8601String(); + + @override + DateTime decode(String raw) => DateTime.parse(raw); +} + +final class _MapCodec extends _SettingsCodec> { + final _SettingsCodec _keyCodec; + final _SettingsCodec _valueCodec; + + const _MapCodec(this._keyCodec, this._valueCodec); + + @override + String encode(Map value) { + final entries = {}; + value.forEach((k, v) => entries[_keyCodec.encode(k)] = _valueCodec.encode(v)); + return jsonEncode(entries); + } + + @override + Map decode(String raw) { + try { + final decoded = jsonDecode(raw); + if (decoded is! Map) { + return {}; + } + final result = {}; + for (final entry in decoded.entries) { + final rawKey = entry.key; + final rawValue = entry.value; + if (rawKey is! String || rawValue is! String) { + return {}; + } + final k = _keyCodec.decode(rawKey); + final v = _valueCodec.decode(rawValue); + result[k] = v; + } + return result; + } on FormatException { + return {}; + } + } +} + +final class _ListCodec extends _SettingsCodec> { + final _SettingsCodec _elementCodec; + + const _ListCodec(this._elementCodec); + + @override + String encode(List value) => jsonEncode(value.map(_elementCodec.encode).toList()); + + @override + List decode(String raw) { + try { + final decoded = jsonDecode(raw); + if (decoded is! List) { + return []; + } + final result = []; + for (final item in decoded) { + if (item is! String) { + return []; + } + final element = _elementCodec.decode(item); + result.add(element); + } + return result; + } on FormatException { + return []; + } + } +} + +final class _PrimitiveCodec extends _SettingsCodec { + final T Function(String) _parse; + + const _PrimitiveCodec._(this._parse); + + @override + String encode(T value) => value.toString(); + + @override + T decode(String raw) => _parse(raw); + + static const integer = _PrimitiveCodec._(int.parse); + static const real = _PrimitiveCodec._(double.parse); + static const boolean = _PrimitiveCodec._(bool.parse); + static const string = _PrimitiveCodec._(_identity); + + static String _identity(String s) => s; +} diff --git a/mobile/lib/domain/models/stack.model.dart b/mobile/lib/domain/models/stack.model.dart index d5ccf5558d..f17f5788c9 100644 --- a/mobile/lib/domain/models/stack.model.dart +++ b/mobile/lib/domain/models/stack.model.dart @@ -37,7 +37,9 @@ class Stack { @override bool operator ==(covariant Stack other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } return other.id == id && other.createdAt == createdAt && @@ -61,7 +63,9 @@ class StackResponse { @override bool operator ==(covariant StackResponse other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } return other.id == id && other.primaryAssetId == primaryAssetId && other.assetIds == assetIds; } diff --git a/mobile/lib/domain/models/store.model.dart b/mobile/lib/domain/models/store.model.dart index 00545aa01a..be1b0c5fb8 100644 --- a/mobile/lib/domain/models/store.model.dart +++ b/mobile/lib/domain/models/store.model.dart @@ -4,97 +4,59 @@ import 'package:immich_mobile/domain/models/user.model.dart'; /// Defines the data type for each value enum StoreKey { version._(0), - assetETag._(1), currentUser._(2), - deviceIdHash._(3), deviceId._(4), - backupFailedSince._(5), - backupRequireWifi._(6), - backupRequireCharging._(7), - backupTriggerDelay._(8), serverUrl._(10), accessToken._(11), serverEndpoint._(12), - autoBackup._(13), - backgroundBackup._(14), - sslClientCertData._(15), - sslClientPasswd._(16), - // user settings from [AppSettingsEnum] below: - loadPreview._(100), - loadOriginal._(101), - themeMode._(102), - tilesPerRow._(103), - dynamicLayout._(104), - groupAssetsBy._(105), - uploadErrorNotificationGracePeriod._(106), - backgroundBackupTotalProgress._(107), - backgroundBackupSingleProgress._(108), - storageIndicator._(109), - thumbnailCacheSize._(110), - imageCacheSize._(111), - albumThumbnailCacheSize._(112), - selectedAlbumSortOrder._(113), advancedTroubleshooting._(114), - logLevel._(115), - preferRemoteImage._(116), - loopVideo._(117), - // map related settings - mapShowFavoriteOnly._(118), - mapRelativeDate._(119), - selfSignedCert._(120), - mapIncludeArchived._(121), - ignoreIcloudAssets._(122), - selectedAlbumSortReverse._(123), - mapThemeMode._(124), - mapwithPartners._(125), enableHapticFeedback._(126), - customHeaders._(127), - // theme settings - primaryColor._(128), - dynamicTheme._(129), - colorfulInterface._(130), - - syncAlbums._(131), - - // Auto endpoint switching - autoEndpointSwitching._(132), - preferredWifiName._(133), - localEndpoint._(134), - externalEndpointList._(135), - - // Video settings - loadOriginalVideo._(136), manageLocalMediaAndroid._(137), - // Read-only Mode settings readonlyModeEnabled._(138), - autoPlayVideo._(139), - albumGridView._(140), + syncMigrationStatus._(1013), - // Image viewer navigation settings - tapToNavigate._(141), - - // Experimental stuff - photoManagerCustomFilter._(1000), - betaPromptShown._(1001), - betaTimeline._(1002), - enableBackup._(1003), - useWifiForUploadVideos._(1004), - useWifiForUploadPhotos._(1005), - needBetaMigration._(1006), - // TODO: Remove this after patching open-api - shouldResetSync._(1007), - - // Free up space - cleanupKeepFavorites._(1008), - cleanupKeepMediaType._(1009), - cleanupKeepAlbumIds._(1010), - cleanupCutoffDaysAgo._(1011), - cleanupDefaultsInitialized._(1012), - - syncMigrationStatus._(1013); + // Legacy keys that have been migrated to the new metadata store + legacyBackupRequireCharging._(7), + legacyBackupTriggerDelay._(8), + legacySyncAlbums._(131), + legacyEnableBackup._(1003), + legacyUseWifiForUploadVideos._(1004), + legacyUseWifiForUploadPhotos._(1005), + legacySelectedAlbumSortOrder._(113), + legacySelectedAlbumSortReverse._(123), + legacyAlbumGridView._(140), + legacyAutoEndpointSwitching._(132), + legacyPreferredWifiName._(133), + legacyLocalEndpoint._(134), + legacyExternalEndpointList._(135), + legacyCustomHeaders._(127), + legacyLoopVideo._(117), + legacyLoadOriginalVideo._(136), + legacyAutoPlayVideo._(139), + legacyTapToNavigate._(141), + legacyPreferRemoteImage._(116), + legacyLoadOriginal._(101), + legacyPrimaryColor._(128), + legacyDynamicTheme._(129), + legacyColorfulInterface._(130), + legacyThemeMode._(102), + legacyCleanupKeepFavorites._(1008), + legacyCleanupKeepMediaType._(1009), + legacyCleanupKeepAlbumIds._(1010), + legacyCleanupCutoffDaysAgo._(1011), + legacyCleanupDefaultsInitialized._(1012), + legacyTilesPerRow._(103), + legacyGroupAssetsBy._(105), + legacyStorageIndicator._(109), + legacyMapRelativeDate._(119), + legacyMapShowFavoriteOnly._(118), + legacyMapIncludeArchived._(121), + legacyMapThemeMode._(124), + legacyMapwithPartners._(125), + legacyLogLevel._(115); const StoreKey._(this.id); final int id; @@ -118,7 +80,9 @@ StoreDto: { @override bool operator ==(covariant StoreDto other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } return other.key == key && other.value == value; } diff --git a/mobile/lib/domain/models/tag.model.dart b/mobile/lib/domain/models/tag.model.dart index 357367b13e..ba9aef02ee 100644 --- a/mobile/lib/domain/models/tag.model.dart +++ b/mobile/lib/domain/models/tag.model.dart @@ -13,7 +13,9 @@ class Tag { @override bool operator ==(covariant Tag other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } return other.id == id && other.value == value; } diff --git a/mobile/lib/domain/models/timeline.model.dart b/mobile/lib/domain/models/timeline.model.dart index c531fa4a94..86f6f112fb 100644 --- a/mobile/lib/domain/models/timeline.model.dart +++ b/mobile/lib/domain/models/timeline.model.dart @@ -2,6 +2,8 @@ enum GroupAssetsBy { day, month, auto, none } enum HeaderType { none, month, day, monthAndDay } +enum SortAssetsBy { taken, uploaded } + class Bucket { final int assetCount; diff --git a/mobile/lib/domain/models/user.model.dart b/mobile/lib/domain/models/user.model.dart index 380295b4b3..9ed70d61d6 100644 --- a/mobile/lib/domain/models/user.model.dart +++ b/mobile/lib/domain/models/user.model.dart @@ -125,7 +125,9 @@ profileChangedAt: $profileChangedAt @override bool operator ==(covariant UserDto other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } return other.id == id && ((updatedAt == null && other.updatedAt == null) || @@ -219,7 +221,9 @@ class PartnerUserDto { @override bool operator ==(covariant PartnerUserDto other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } return other.id == id && other.email == email && diff --git a/mobile/lib/domain/models/user_metadata.model.dart b/mobile/lib/domain/models/user_metadata.model.dart index af404051a7..3da1d94799 100644 --- a/mobile/lib/domain/models/user_metadata.model.dart +++ b/mobile/lib/domain/models/user_metadata.model.dart @@ -35,7 +35,9 @@ isOnboarded: $isOnboarded, @override bool operator ==(covariant Onboarding other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } return isOnboarded == other.isOnboarded; } @@ -132,7 +134,9 @@ showSupportBadge: $showSupportBadge, @override bool operator ==(covariant Preferences other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } return other.foldersEnabled == foldersEnabled && other.memoriesEnabled == memoriesEnabled && @@ -199,7 +203,9 @@ licenseKey: $licenseKey, @override bool operator ==(covariant License other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } return activatedAt == other.activatedAt && activationKey == other.activationKey && licenseKey == other.licenseKey; } @@ -251,7 +257,9 @@ license: ${license ?? ""}, @override bool operator ==(covariant UserMetadata other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } return other.userId == userId && other.key == key && diff --git a/mobile/lib/domain/services/asset.service.dart b/mobile/lib/domain/services/asset.service.dart index 7fa8c13fd8..b055ad38b1 100644 --- a/mobile/lib/domain/services/asset.service.dart +++ b/mobile/lib/domain/services/asset.service.dart @@ -8,11 +8,7 @@ class AssetService { final RemoteAssetRepository _remoteAssetRepository; final DriftLocalAssetRepository _localAssetRepository; - const AssetService({ - required RemoteAssetRepository remoteAssetRepository, - required DriftLocalAssetRepository localAssetRepository, - }) : _remoteAssetRepository = remoteAssetRepository, - _localAssetRepository = localAssetRepository; + const AssetService({required this._remoteAssetRepository, required this._localAssetRepository}); Future getAsset(BaseAsset asset) { final id = asset is LocalAsset ? asset.id : (asset as RemoteAsset).id; diff --git a/mobile/lib/domain/services/background_worker.service.dart b/mobile/lib/domain/services/background_worker.service.dart index d4da3e31a4..529ec770c4 100644 --- a/mobile/lib/domain/services/background_worker.service.dart +++ b/mobile/lib/domain/services/background_worker.service.dart @@ -11,15 +11,14 @@ import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/platform_extensions.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/logger_db.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart'; import 'package:immich_mobile/platform/background_worker_api.g.dart'; import 'package:immich_mobile/platform/background_worker_lock_api.g.dart'; -import 'package:immich_mobile/providers/app_settings.provider.dart'; import 'package:immich_mobile/providers/background_sync.provider.dart'; import 'package:immich_mobile/providers/backup/drift_backup.provider.dart'; import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; import 'package:immich_mobile/providers/infrastructure/platform.provider.dart' show nativeSyncApiProvider; import 'package:immich_mobile/providers/user.provider.dart'; -import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/services/auth.service.dart'; import 'package:immich_mobile/services/foreground_upload.service.dart'; import 'package:immich_mobile/services/localization.service.dart'; @@ -39,16 +38,15 @@ class BackgroundWorkerFgService { Future saveNotificationMessage(String title, String body) => _foregroundHostApi.saveNotificationMessage(title, body); - Future configure({int? minimumDelaySeconds, bool? requireCharging}) => _foregroundHostApi.configure( - BackgroundWorkerSettings( - minimumDelaySeconds: - minimumDelaySeconds ?? - Store.get(AppSettingsEnum.backupTriggerDelay.storeKey, AppSettingsEnum.backupTriggerDelay.defaultValue), - requiresCharging: - requireCharging ?? - Store.get(AppSettingsEnum.backupRequireCharging.storeKey, AppSettingsEnum.backupRequireCharging.defaultValue), - ), - ); + Future configure({int? minimumDelaySeconds, bool? requireCharging}) { + final backup = SettingsRepository.instance.appConfig.backup; + return _foregroundHostApi.configure( + BackgroundWorkerSettings( + minimumDelaySeconds: minimumDelaySeconds ?? backup.triggerDelay, + requiresCharging: requireCharging ?? backup.requireCharging, + ), + ); + } Future disable() => _foregroundHostApi.disable(); } @@ -63,15 +61,13 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi { bool _isCleanedUp = false; - BackgroundWorkerBgService({required Drift drift, required DriftLogger driftLogger}) - : _drift = drift, - _driftLogger = driftLogger, - _backgroundHostApi = BackgroundWorkerBgHostApi() { - _ref = ProviderContainer(overrides: [driftProvider.overrideWith(driftOverride(drift))]); + BackgroundWorkerBgService({required this._drift, required this._driftLogger}) + : _backgroundHostApi = BackgroundWorkerBgHostApi() { + _ref = ProviderContainer(overrides: [driftProvider.overrideWith(driftOverride(_drift))]); BackgroundWorkerFlutterApi.setUp(this); } - bool get _isBackupEnabled => _ref?.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.enableBackup) ?? false; + bool get _isBackupEnabled => SettingsRepository.instance.appConfig.backup.enabled; Future init() async { try { @@ -105,22 +101,14 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi { } @override - Future onAndroidUpload() async { - _logger.info('Android background processing started'); - final sw = Stopwatch()..start(); - try { - if (!await _syncAssets(hashTimeout: Duration(minutes: _isBackupEnabled ? 3 : 6))) { - _logger.warning("Remote sync did not complete successfully, skipping backup"); - return; - } - await _handleBackup(); - } catch (error, stack) { - _logger.severe("Failed to complete Android background processing", error, stack); - } finally { - sw.stop(); - _logger.info("Android background processing completed in ${sw.elapsed.inSeconds}s"); - await _cleanup(); - } + Future onAndroidUpload(int? maxMinutes) async { + final hashTimeout = Duration(minutes: _isBackupEnabled ? 3 : 6); + final backupTimeout = maxMinutes != null ? Duration(minutes: maxMinutes - 1) : null; + return _backgroundLoop( + hashTimeout: hashTimeout, + backupTimeout: backupTimeout, + debugLabel: 'Android background upload', + ); } @override @@ -128,17 +116,24 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi { _logger.info('iOS background upload started with maxSeconds: ${maxSeconds}s'); final sw = Stopwatch()..start(); try { - final timeout = isRefresh ? const Duration(seconds: 5) : Duration(minutes: _isBackupEnabled ? 3 : 6); - if (!await _syncAssets(hashTimeout: timeout)) { - _logger.warning("Remote sync did not complete successfully, skipping backup"); + final budget = maxSeconds != null ? Duration(seconds: maxSeconds - 1) : null; + + final sync = _ref?.read(backgroundSyncProvider); + if (sync == null) { return; } - final backupFuture = _handleBackup(); - if (maxSeconds != null) { - await backupFuture.timeout(Duration(seconds: maxSeconds - 1), onTimeout: () {}); + // Run sync local, sync remote, hash and backup concurrently so the bg + // refresh task (20s budget) can make progress on all four instead of + // racing them sequentially. Phases are independent at the data layer: + // hash and handle_backup read drift state and tolerate stale reads + // (server-side dedup catches the rare race). The single budget caps the + // whole batch; no phase needs its own timeout. + final all = Future.wait([sync.syncLocal(), sync.syncRemote(), sync.hashAssets(), _handleBackup()]); + if (budget != null) { + await all.timeout(budget, onTimeout: () => []); } else { - await backupFuture; + await all; } } catch (error, stack) { _logger.severe("Failed to complete iOS background upload", error, stack); @@ -149,6 +144,45 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi { } } + Future _backgroundLoop({ + required Duration hashTimeout, + required Duration? backupTimeout, + required String debugLabel, + }) async { + _logger.info( + '$debugLabel started hashTimeout: ${hashTimeout.inSeconds}s, backupTimeout: ${backupTimeout?.inMinutes ?? '~'}m', + ); + final sw = Stopwatch()..start(); + try { + if (!await _syncAssets(hashTimeout: hashTimeout)) { + _logger.warning("Remote sync did not complete successfully, skipping backup"); + return; + } + + final backupFuture = _handleBackup(); + Timer? cancelTimer; + if (backupTimeout != null) { + cancelTimer = Timer(backupTimeout, () { + if (!_cancellationToken.isCompleted) { + _logger.warning("$debugLabel timed out after ${backupTimeout.inMinutes}m, cancelling backup"); + _cancellationToken.complete(); + } + }); + } + try { + await backupFuture; + } finally { + cancelTimer?.cancel(); + } + } catch (error, stack) { + _logger.severe("Failed to complete $debugLabel", error, stack); + } finally { + sw.stop(); + _logger.info("$debugLabel completed in ${sw.elapsed.inSeconds}s"); + await _cleanup(); + } + } + @override Future cancel() async { _logger.warning("Background worker cancelled"); @@ -176,29 +210,23 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi { final backgroundSyncManager = _ref?.read(backgroundSyncProvider); final nativeSyncApi = _ref?.read(nativeSyncApiProvider); + _logger.info("Cleaning up background worker"); + if (!_cancellationToken.isCompleted) { + _cancellationToken.complete(); + } + + // Workers share one sqlite connection, so DB teardown must wait until every worker has stopped using it. + await Future.wait([ + if (backgroundSyncManager != null) backgroundSyncManager.cancel(), + if (nativeSyncApi != null) nativeSyncApi.cancelHashing(), + ]); + await workerManagerPatch.dispose().catchError((_) async {}); + await Future.wait([LogService.I.dispose(), Store.dispose(), _drift.optimize(allTables: true)]); await _drift.close(); await _driftLogger.close(); _ref?.dispose(); _ref = null; - - _cancellationToken.complete(); - _logger.info("Cleaning up background worker"); - - final cleanupFutures = [ - nativeSyncApi?.cancelHashing(), - workerManagerPatch.dispose().catchError((_) async { - // Discard any errors on the dispose call - return; - }), - LogService.I.dispose(), - Store.dispose(), - - backgroundSyncManager?.cancel(), - ]; - - await Future.wait(cleanupFutures.nonNulls); - _logger.info("Background worker resources cleaned up"); } catch (error, stack) { dPrint(() => 'Failed to cleanup background worker: $error with stack: $stack'); } diff --git a/mobile/lib/domain/services/hash.service.dart b/mobile/lib/domain/services/hash.service.dart index 6781507566..e4c332b283 100644 --- a/mobile/lib/domain/services/hash.service.dart +++ b/mobile/lib/domain/services/hash.service.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:flutter/services.dart'; import 'package:immich_mobile/constants/constants.dart'; import 'package:immich_mobile/domain/models/album/local_album.model.dart'; @@ -17,24 +19,23 @@ class HashService { final DriftLocalAssetRepository _localAssetRepository; final DriftTrashedLocalAssetRepository _trashedLocalAssetRepository; final NativeSyncApi _nativeSyncApi; - final bool Function()? _cancelChecker; + final Completer? _cancellation; final _log = Logger('HashService'); HashService({ - required DriftLocalAlbumRepository localAlbumRepository, - required DriftLocalAssetRepository localAssetRepository, - required DriftTrashedLocalAssetRepository trashedLocalAssetRepository, - required NativeSyncApi nativeSyncApi, - bool Function()? cancelChecker, + required this._localAlbumRepository, + required this._localAssetRepository, + required this._trashedLocalAssetRepository, + required this._nativeSyncApi, + this._cancellation, int? batchSize, - }) : _localAlbumRepository = localAlbumRepository, - _localAssetRepository = localAssetRepository, - _trashedLocalAssetRepository = trashedLocalAssetRepository, - _cancelChecker = cancelChecker, - _nativeSyncApi = nativeSyncApi, - _batchSize = batchSize ?? kBatchHashFileLimit; + }) : _batchSize = batchSize ?? kBatchHashFileLimit { + // Stop the in-flight native hash call promptly on cancellation; the loops + // below also observe [isCancelled] to bail between batches. + _cancellation?.future.then((_) => _nativeSyncApi.cancelHashing().onError(_log.warning)); + } - bool get isCancelled => _cancelChecker?.call() ?? false; + bool get isCancelled => _cancellation?.isCompleted ?? false; Future hashAssets() async { _log.info("Starting hashing of assets"); diff --git a/mobile/lib/domain/services/local_sync.service.dart b/mobile/lib/domain/services/local_sync.service.dart index 1d9ab1e490..feb104f90d 100644 --- a/mobile/lib/domain/services/local_sync.service.dart +++ b/mobile/lib/domain/services/local_sync.service.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; import 'package:immich_mobile/domain/models/album/local_album.model.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; @@ -9,43 +10,46 @@ import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/platform_extensions.dart'; import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/trashed_local_asset.repository.dart'; import 'package:immich_mobile/platform/native_sync_api.g.dart'; -import 'package:immich_mobile/repositories/local_files_manager.repository.dart'; +import 'package:immich_mobile/repositories/asset_media.repository.dart'; +import 'package:immich_mobile/repositories/permission.repository.dart'; import 'package:immich_mobile/utils/datetime_helpers.dart'; import 'package:immich_mobile/utils/diff.dart'; import 'package:logging/logging.dart'; +const String _kSyncCancelledCode = "SYNC_CANCELLED"; + class LocalSyncService { final DriftLocalAlbumRepository _localAlbumRepository; // ignore: unused_field final DriftLocalAssetRepository _localAssetRepository; final NativeSyncApi _nativeSyncApi; final DriftTrashedLocalAssetRepository _trashedLocalAssetRepository; - final LocalFilesManagerRepository _localFilesManager; - final StorageRepository _storageRepository; + final AssetMediaRepository _assetMediaRepository; + final IPermissionRepository _permissionRepository; + final Completer? _cancellation; final Logger _log = Logger("DeviceSyncService"); LocalSyncService({ - required DriftLocalAlbumRepository localAlbumRepository, - required DriftLocalAssetRepository localAssetRepository, - required DriftTrashedLocalAssetRepository trashedLocalAssetRepository, - required LocalFilesManagerRepository localFilesManager, - required StorageRepository storageRepository, - required NativeSyncApi nativeSyncApi, - }) : _localAlbumRepository = localAlbumRepository, - _localAssetRepository = localAssetRepository, - _trashedLocalAssetRepository = trashedLocalAssetRepository, - _localFilesManager = localFilesManager, - _storageRepository = storageRepository, - _nativeSyncApi = nativeSyncApi; + required this._localAlbumRepository, + required this._localAssetRepository, + required this._nativeSyncApi, + required this._trashedLocalAssetRepository, + required this._assetMediaRepository, + required this._permissionRepository, + this._cancellation, + }) { + _cancellation?.future.then((_) => _nativeSyncApi.cancelSync().onError(_log.warning)); + } + + bool get _isCancelled => _cancellation?.isCompleted ?? false; Future sync({bool full = false}) async { final Stopwatch stopwatch = Stopwatch()..start(); try { if (CurrentPlatform.isAndroid && Store.get(StoreKey.manageLocalMediaAndroid, false)) { - final hasPermission = await _localFilesManager.hasManageMediaPermission(); + final hasPermission = await _permissionRepository.hasManageMediaPermission(); if (hasPermission) { await _syncTrashedAssets(); } else { @@ -86,6 +90,10 @@ class LocalSyncService { // detect album deletions from the native side if (CurrentPlatform.isAndroid) { for (final album in dbAlbums) { + if (_isCancelled) { + _log.warning("Local sync cancelled. Stopped processing albums."); + return; + } final deviceIds = await _nativeSyncApi.getAssetIdsForAlbum(album.id); await _localAlbumRepository.syncDeletes(album.id, deviceIds); } @@ -93,10 +101,13 @@ class LocalSyncService { if (CurrentPlatform.isIOS) { // On iOS, we need to full sync albums that are marked as cloud as the delta sync - // does not include changes for cloud albums. If ignoreIcloudAssets is enabled, - // remove the albums from the local database from the previous sync + // does not include changes for cloud albums. final cloudAlbums = deviceAlbums.where((a) => a.isCloud).toLocalAlbums(); for (final album in cloudAlbums) { + if (_isCancelled) { + _log.warning("Local sync cancelled. Stopped processing cloud albums."); + return; + } final dbAlbum = dbAlbums.firstWhereOrNull((a) => a.id == album.id); if (dbAlbum == null) { _log.warning("Cloud album ${album.name} not found in local database. Skipping sync."); @@ -108,6 +119,12 @@ class LocalSyncService { await _mapIosCloudIds(newAssets); } await _nativeSyncApi.checkpointSync(); + } on PlatformException catch (e, s) { + if (e.code == _kSyncCancelledCode) { + _log.warning("Local sync cancelled"); + } else { + _log.severe("Error performing device sync", e, s); + } } catch (e, s) { _log.severe("Error performing device sync", e, s); } finally { @@ -135,12 +152,21 @@ class LocalSyncService { await _nativeSyncApi.checkpointSync(); stopwatch.stop(); _log.info("Full device sync took - ${stopwatch.elapsedMilliseconds}ms"); + } on PlatformException catch (e, s) { + if (e.code == _kSyncCancelledCode) { + _log.warning("Full device sync cancelled"); + } else { + _log.severe("Error performing full device sync", e, s); + } } catch (e, s) { _log.severe("Error performing full device sync", e, s); } } Future addAlbum(LocalAlbum album) async { + if (_isCancelled) { + return; + } try { _log.fine("Adding device album ${album.name}"); @@ -168,6 +194,9 @@ class LocalSyncService { // The deviceAlbum is ignored since we are going to refresh it anyways FutureOr updateAlbum(LocalAlbum dbAlbum, LocalAlbum deviceAlbum) async { + if (_isCancelled) { + return false; + } try { _log.fine("Syncing device album ${dbAlbum.name}"); @@ -374,7 +403,7 @@ class LocalSyncService { final assetsToRestore = await _trashedLocalAssetRepository.getToRestore(); if (assetsToRestore.isNotEmpty) { - final restoredIds = await _localFilesManager.restoreAssetsFromTrash(assetsToRestore); + final restoredIds = await _assetMediaRepository.restoreAssetsFromTrash(assetsToRestore); await _trashedLocalAssetRepository.applyRestoredAssets(restoredIds); } else { _log.info("syncTrashedAssets, No remote assets found for restoration"); @@ -382,15 +411,15 @@ class LocalSyncService { final localAssetsToTrash = await _trashedLocalAssetRepository.getToTrash(); if (localAssetsToTrash.isNotEmpty) { - final mediaUrls = await Future.wait( - localAssetsToTrash.values - .expand((e) => e) - .map((localAsset) => _storageRepository.getAssetEntityForAsset(localAsset).then((e) => e?.getMediaUrl())), - ); - _log.info("Moving to trash ${mediaUrls.join(", ")} assets"); - final result = await _localFilesManager.moveToTrash(mediaUrls.nonNulls.toList()); - if (result) { - await _trashedLocalAssetRepository.trashLocalAsset(localAssetsToTrash); + final localIds = localAssetsToTrash.values.expand((assets) => assets).map((asset) => asset.id).toList(); + _log.info("Moving to trash ${localIds.join(", ")} assets"); + final movedIds = await _assetMediaRepository.deleteAll(localIds); + if (movedIds.isNotEmpty) { + final movedAssetsByAlbum = localAssetsToTrash.map( + (albumId, assets) => MapEntry(albumId, assets.where((asset) => movedIds.contains(asset.id)).toList()), + )..removeWhere((_, assets) => assets.isEmpty); + + await _trashedLocalAssetRepository.trashLocalAsset(movedAssetsByAlbum); } } else { _log.info("syncTrashedAssets, No assets found in backup-enabled albums for move to trash"); diff --git a/mobile/lib/domain/services/log.service.dart b/mobile/lib/domain/services/log.service.dart index b58ee89535..b612b3ce91 100644 --- a/mobile/lib/domain/services/log.service.dart +++ b/mobile/lib/domain/services/log.service.dart @@ -2,20 +2,20 @@ import 'dart:async'; import 'package:immich_mobile/constants/constants.dart'; import 'package:immich_mobile/domain/models/log.model.dart'; -import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/domain/models/settings_key.dart'; import 'package:immich_mobile/infrastructure/repositories/log.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/store.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart'; import 'package:immich_mobile/utils/debug_print.dart'; import 'package:logging/logging.dart'; /// Service responsible for handling application logging. /// /// It listens to Dart's [Logger.root], buffers logs in memory (optionally), -/// writes them to a persistent [ILogRepository], and manages log levels -/// via [IStoreRepository] +/// writes them to a persistent [LogRepository], and manages log levels via +/// [SettingsRepository]. class LogService { final LogRepository _logRepository; - final DriftStoreRepository _storeRepository; + final SettingsRepository _settingsRepository; final List _msgBuffer = []; @@ -38,12 +38,12 @@ class LogService { static Future init({ required LogRepository logRepository, - required DriftStoreRepository storeRepository, + required SettingsRepository settingsRepository, bool shouldBuffer = true, }) async { _instance ??= await create( logRepository: logRepository, - storeRepository: storeRepository, + settingsRepository: settingsRepository, shouldBuffer: shouldBuffer, ); return _instance!; @@ -51,17 +51,17 @@ class LogService { static Future create({ required LogRepository logRepository, - required DriftStoreRepository storeRepository, + required SettingsRepository settingsRepository, bool shouldBuffer = true, }) async { - final instance = LogService._(logRepository, storeRepository, shouldBuffer); + final instance = LogService._(logRepository, settingsRepository, shouldBuffer); await logRepository.truncate(limit: kLogTruncateLimit); - final level = await instance._storeRepository.tryGet(StoreKey.logLevel) ?? LogLevel.info.index; - Logger.root.level = Level.LEVELS.elementAtOrNull(level) ?? Level.INFO; + final level = instance._settingsRepository.appConfig.logLevel; + Logger.root.level = Level.LEVELS.elementAtOrNull(level.index) ?? Level.INFO; return instance; } - LogService._(this._logRepository, this._storeRepository, this._shouldBuffer) { + LogService._(this._logRepository, this._settingsRepository, this._shouldBuffer) { _logSubscription = Logger.root.onRecord.listen(_handleLogRecord); } @@ -91,7 +91,7 @@ class LogService { } Future setLogLevel(LogLevel level) async { - await _storeRepository.upsert(StoreKey.logLevel, level.index); + await _settingsRepository.write(SettingsKey.logLevel, level); Logger.root.level = level.toLevel(); } @@ -112,10 +112,16 @@ class LogService { return _flushBuffer(); } - Future dispose() { + Future dispose() async { _flushTimer?.cancel(); - _logSubscription.cancel(); - return _flushBuffer(); + _flushTimer = null; + await _logSubscription.cancel(); + await _flushBuffer(); + // Allow a subsequent init() (e.g. when a worker isolate is reused) to + // create a fresh instance instead of returning this disposed one. + if (identical(_instance, this)) { + _instance = null; + } } Future _flushBuffer() async { diff --git a/mobile/lib/domain/services/map.service.dart b/mobile/lib/domain/services/map.service.dart index 6c64e2817e..6c484d1a14 100644 --- a/mobile/lib/domain/services/map.service.dart +++ b/mobile/lib/domain/services/map.service.dart @@ -10,7 +10,7 @@ typedef MapQuery = ({MapMarkerSource markerSource}); class MapFactory { final DriftMapRepository _mapRepository; - const MapFactory({required DriftMapRepository mapRepository}) : _mapRepository = mapRepository; + const MapFactory({required this._mapRepository}); MapService remote(List ownerIds, TimelineMapOptions options) => MapService(_mapRepository.remote(ownerIds, options)); diff --git a/mobile/lib/domain/services/remote_album.service.dart b/mobile/lib/domain/services/remote_album.service.dart index f060ba9290..3c8999decd 100644 --- a/mobile/lib/domain/services/remote_album.service.dart +++ b/mobile/lib/domain/services/remote_album.service.dart @@ -8,13 +8,49 @@ import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/infrastructure/repositories/remote_album.repository.dart'; import 'package:immich_mobile/models/albums/album_search.model.dart'; import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart'; +import 'package:openapi/api.dart' show Optional; import 'package:immich_mobile/repositories/drift_album_api_repository.dart'; +import 'package:immich_mobile/services/foreground_upload.service.dart'; +import 'package:logging/logging.dart'; + +/// Categorizes a heterogeneous asset selection into the candidates that can +/// be added to an album immediately (already on the server) and the local-only +/// candidates that must be uploaded first. +class AlbumAssetCandidates { + final List remoteAssetIds; + final List localAssetsToUpload; + + const AlbumAssetCandidates({required this.remoteAssetIds, required this.localAssetsToUpload}); +} class RemoteAlbumService { + static final _logger = Logger('RemoteAlbumService'); + final DriftRemoteAlbumRepository _repository; final DriftAlbumApiRepository _albumApiRepository; + final ForegroundUploadService _uploadService; - const RemoteAlbumService(this._repository, this._albumApiRepository); + const RemoteAlbumService(this._repository, this._albumApiRepository, this._uploadService); + + /// Categorizes a heterogeneous asset selection into already-on-server IDs + /// and local assets that still need to be uploaded. + static AlbumAssetCandidates categorizeCandidates(Iterable assets) { + final remoteIds = []; + final localToUpload = []; + for (final asset in assets) { + if (asset is RemoteAsset) { + remoteIds.add(asset.id); + } else if (asset is LocalAsset) { + final remoteId = asset.remoteId; + if (remoteId != null) { + remoteIds.add(remoteId); + } else { + localToUpload.add(asset); + } + } + } + return AlbumAssetCandidates(remoteAssetIds: remoteIds, localAssetsToUpload: localToUpload); + } Stream watchAlbum(String albumId) { return _repository.watchAlbum(albumId); @@ -102,7 +138,7 @@ class RemoteAlbumService { Future updateAlbum( String albumId, { String? name, - String? description, + Optional description = const Optional.absent(), String? thumbnailAssetId, bool? isActivityEnabled, AlbumAssetOrder? order, @@ -148,6 +184,109 @@ class RemoteAlbumService { return album.added.length; } + /// !TODO The name here is not clear as we have addAssets method above, + /// which is only add remote assets to album, for the next PR, we will allow + /// adding local assets from album from the timeline as well with this flow. + /// So saving that for the next refactor + Future addAssetsToAlbum({ + required String albumId, + required UserDto uploader, + required AlbumAssetCandidates candidates, + UploadCallbacks uploadCallbacks = const UploadCallbacks(), + Completer? cancelToken, + }) async { + int addedCount = 0; + if (candidates.remoteAssetIds.isNotEmpty) { + addedCount += await addAssets(albumId: albumId, assetIds: candidates.remoteAssetIds); + } + if (candidates.localAssetsToUpload.isNotEmpty) { + addedCount += await _uploadAndAddLocals( + albumId, + uploader, + candidates.localAssetsToUpload, + uploadCallbacks, + cancelToken, + ); + } + return addedCount; + } + + Future _uploadAndAddLocals( + String albumId, + UserDto uploader, + List localAssets, + UploadCallbacks userCallbacks, + Completer? cancelToken, + ) async { + int addedCount = 0; + final pendingAdds = >[]; + final localById = {for (final a in localAssets) a.id: a}; + + final wrappedCallbacks = UploadCallbacks( + onProgress: (localId, filename, bytes, totalBytes) => _runUploadCallback( + 'Upload progress callback failed for $localId', + () => userCallbacks.onProgress?.call(localId, filename, bytes, totalBytes), + ), + onICloudProgress: (localId, progress) => _runUploadCallback( + 'iCloud progress callback failed for $localId', + () => userCallbacks.onICloudProgress?.call(localId, progress), + ), + onError: (localId, errorMessage) => _runUploadCallback( + 'Upload error callback failed for $localId', + () => userCallbacks.onError?.call(localId, errorMessage), + ), + onSuccess: (localId, remoteId) { + _runUploadCallback( + 'Upload success callback failed for $localId', + () => userCallbacks.onSuccess?.call(localId, remoteId), + ); + final source = localById[localId]; + if (source == null) { + _logger.warning('Upload success for $localId but source LocalAsset missing; skipping album link'); + return; + } + pendingAdds.add( + linkUploadedAssetToAlbum(albumId, remoteId, uploader, source) + .then((added) { + addedCount += added; + }) + .catchError((Object error, StackTrace stack) { + _logger.warning('Failed to add uploaded asset $remoteId to album $albumId', error, stack); + }), + ); + }, + ); + + await _uploadService.uploadManual(localAssets, callbacks: wrappedCallbacks, cancelToken: cancelToken); + await Future.wait(pendingAdds); + return addedCount; + } + + void _runUploadCallback(String message, void Function() callback) { + try { + callback(); + } catch (error, stack) { + _logger.warning(message, error, stack); + } + } + + /// Links a freshly-uploaded asset to an album, ensuring the local DB + /// reflects the change without waiting for the next sync. We call the API + /// (server is the source of truth), then upsert a placeholder + /// `remote_asset_entity` row from the local source so the FK-protected + /// junction insert succeeds. Sync overwrites the placeholder later with + /// the authoritative server data. + Future linkUploadedAssetToAlbum(String albumId, String remoteId, UserDto uploader, LocalAsset source) async { + final result = await _albumApiRepository.addAssets(albumId, [remoteId]); + if (result.added.isEmpty) { + return 0; + } + + await _repository.upsertRemoteAssetStub(remoteId: remoteId, ownerId: uploader.id, source: source); + await _repository.addAssets(albumId, result.added); + return result.added.length; + } + Future deleteAlbum(String albumId) async { await _albumApiRepository.deleteAlbum(albumId); @@ -184,7 +323,9 @@ class RemoteAlbumService { List albums, { required AssetDateAggregation aggregation, }) async { - if (albums.isEmpty) return []; + if (albums.isEmpty) { + return []; + } final albumIds = albums.map((e) => e.id).toList(); final sortedIds = await _repository.getSortedAlbumIds(albumIds, aggregation: aggregation); diff --git a/mobile/lib/domain/services/setting.service.dart b/mobile/lib/domain/services/setting.service.dart index 99e07a2872..1823e46cf9 100644 --- a/mobile/lib/domain/services/setting.service.dart +++ b/mobile/lib/domain/services/setting.service.dart @@ -9,7 +9,7 @@ final AppSetting = SettingsService(storeService: StoreService.I); class SettingsService { final StoreService _storeService; - const SettingsService({required StoreService storeService}) : _storeService = storeService; + const SettingsService({required this._storeService}); T get(Setting setting) => _storeService.get(setting.storeKey, setting.defaultValue); diff --git a/mobile/lib/domain/services/store.service.dart b/mobile/lib/domain/services/store.service.dart index b325ffd631..758622a43b 100644 --- a/mobile/lib/domain/services/store.service.dart +++ b/mobile/lib/domain/services/store.service.dart @@ -54,7 +54,13 @@ class StoreService { /// Disposes the store and cancels the subscription. To reuse the store call init() again Future dispose() async { await _storeUpdateSubscription?.cancel(); + _storeUpdateSubscription = null; _cache.clear(); + // Allow a subsequent init() (e.g. when a worker isolate is reused) to + // create a fresh instance instead of returning this disposed one. + if (identical(_instance, this)) { + _instance = null; + } } /// Returns the cached value for [key], or `null` @@ -72,7 +78,9 @@ class StoreService { /// Stores the [value] for the [key]. Skips write if value hasn't changed. Future put, T>(U key, T value) async { - if (_cache[key.id] == value) return; + if (_cache[key.id] == value) { + return; + } await _storeRepository.upsert(key, value); _cache[key.id] = value; } diff --git a/mobile/lib/domain/services/sync_linked_album.service.dart b/mobile/lib/domain/services/sync_linked_album.service.dart index 3bc76083b8..ddcd6721d7 100644 --- a/mobile/lib/domain/services/sync_linked_album.service.dart +++ b/mobile/lib/domain/services/sync_linked_album.service.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/album/local_album.model.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; @@ -5,6 +7,7 @@ import 'package:immich_mobile/domain/services/store.service.dart'; import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/remote_album.repository.dart'; import 'package:immich_mobile/providers/infrastructure/album.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/cancel.provider.dart'; import 'package:immich_mobile/providers/infrastructure/store.provider.dart'; import 'package:immich_mobile/repositories/drift_album_api_repository.dart'; import 'package:immich_mobile/utils/debug_print.dart'; @@ -16,6 +19,7 @@ final syncLinkedAlbumServiceProvider = Provider( ref.watch(remoteAlbumRepository), ref.watch(driftAlbumApiRepositoryProvider), ref.watch(storeServiceProvider), + cancellation: ref.watch(cancellationProvider), ), ); @@ -24,13 +28,15 @@ class SyncLinkedAlbumService { final DriftRemoteAlbumRepository _remoteAlbumRepository; final DriftAlbumApiRepository _albumApiRepository; final StoreService _storeService; + final Completer? _cancellation; SyncLinkedAlbumService( this._localAlbumRepository, this._remoteAlbumRepository, this._albumApiRepository, - this._storeService, - ); + this._storeService, { + this._cancellation, + }); final _log = Logger("SyncLinkedAlbumService"); @@ -55,7 +61,11 @@ class SyncLinkedAlbumService { final assetIds = await _remoteAlbumRepository.getLinkedAssetIds(userId, localAlbum.id, linkedRemoteAlbumId); _log.fine("Syncing ${assetIds.length} assets to remote album: ${remoteAlbum.name}"); if (assetIds.isNotEmpty) { - final album = await _albumApiRepository.addAssets(remoteAlbum.id, assetIds); + final album = await _albumApiRepository.addAssets( + remoteAlbum.id, + assetIds, + abortTrigger: _cancellation?.future, + ); await _remoteAlbumRepository.addAssets(remoteAlbum.id, album.added); } }), diff --git a/mobile/lib/domain/services/sync_stream.service.dart b/mobile/lib/domain/services/sync_stream.service.dart index 51999a69d5..5a197dc88a 100644 --- a/mobile/lib/domain/services/sync_stream.service.dart +++ b/mobile/lib/domain/services/sync_stream.service.dart @@ -9,12 +9,12 @@ import 'package:immich_mobile/domain/models/sync_event.model.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/platform_extensions.dart'; import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/sync_api.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/sync_migration.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/sync_stream.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/trashed_local_asset.repository.dart'; -import 'package:immich_mobile/repositories/local_files_manager.repository.dart'; +import 'package:immich_mobile/repositories/asset_media.repository.dart'; +import 'package:immich_mobile/repositories/permission.repository.dart'; import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/utils/semver.dart'; import 'package:logging/logging.dart'; @@ -24,6 +24,7 @@ enum SyncMigrationTask { v20260128_ResetExifV1, // EXIF table has incorrect width and height information. v20260128_CopyExifWidthHeightToAsset, // Asset table has incorrect width and height for video ratio calculations. v20260128_ResetAssetV1, // Asset v2.5.0 has width and height information that were edited assets. + v20260597_ResetAssetV1AssetV2, // Assets didn't include the uploadedAt column. } class SyncStreamService { @@ -33,33 +34,25 @@ class SyncStreamService { final SyncStreamRepository _syncStreamRepository; final DriftLocalAssetRepository _localAssetRepository; final DriftTrashedLocalAssetRepository _trashedLocalAssetRepository; - final LocalFilesManagerRepository _localFilesManager; - final StorageRepository _storageRepository; + final AssetMediaRepository _assetMediaRepository; + final IPermissionRepository _permissionRepository; final SyncMigrationRepository _syncMigrationRepository; final ApiService _api; - final bool Function()? _cancelChecker; + final Completer? _cancellation; SyncStreamService({ - required SyncApiRepository syncApiRepository, - required SyncStreamRepository syncStreamRepository, - required DriftLocalAssetRepository localAssetRepository, - required DriftTrashedLocalAssetRepository trashedLocalAssetRepository, - required LocalFilesManagerRepository localFilesManager, - required StorageRepository storageRepository, - required SyncMigrationRepository syncMigrationRepository, - required ApiService api, - bool Function()? cancelChecker, - }) : _syncApiRepository = syncApiRepository, - _syncStreamRepository = syncStreamRepository, - _localAssetRepository = localAssetRepository, - _trashedLocalAssetRepository = trashedLocalAssetRepository, - _localFilesManager = localFilesManager, - _storageRepository = storageRepository, - _syncMigrationRepository = syncMigrationRepository, - _api = api, - _cancelChecker = cancelChecker; + required this._syncApiRepository, + required this._syncStreamRepository, + required this._localAssetRepository, + required this._trashedLocalAssetRepository, + required this._assetMediaRepository, + required this._permissionRepository, + required this._syncMigrationRepository, + required this._api, + this._cancellation, + }); - bool get isCancelled => _cancelChecker?.call() ?? false; + bool get isCancelled => _cancellation?.isCompleted ?? false; Future sync() async { _logger.info("Remote sync request for user"); @@ -87,10 +80,15 @@ class SyncStreamService { _handleEvents, serverVersion: serverSemVer, onReset: () => shouldReset = true, + abortSignal: _cancellation?.future, ); if (shouldReset) { _logger.info("Resetting sync state as requested by server"); - await _syncApiRepository.streamChanges(_handleEvents, serverVersion: serverSemVer); + await _syncApiRepository.streamChanges( + _handleEvents, + serverVersion: serverSemVer, + abortSignal: _cancellation?.future, + ); } previousLength = migrations.length; @@ -132,6 +130,13 @@ class SyncStreamService { migrations.add(SyncMigrationTask.v20260128_CopyExifWidthHeightToAsset.name); } } + + if (!migrations.contains(SyncMigrationTask.v20260597_ResetAssetV1AssetV2.name) && + semVer > const SemVer(major: 2, minor: 7, patch: 5)) { + _logger.info("Running pre-sync task: v20260597_ResetAssetV1AssetV2"); + await _syncApiRepository.deleteSyncAck([SyncEntityType.assetV1, SyncEntityType.assetV2]); + migrations.add(SyncMigrationTask.v20260597_ResetAssetV1AssetV2.name); + } } Future _runPostSyncTasks(List migrations) async { @@ -322,7 +327,9 @@ class SyncStreamService { } Future handleWsAssetUploadReadyV1Batch(List batchData) async { - if (batchData.isEmpty) return; + if (batchData.isEmpty || isCancelled) { + return; + } _logger.info('Processing batch of ${batchData.length} AssetUploadReadyV1 events'); @@ -363,7 +370,9 @@ class SyncStreamService { } Future handleWsAssetUploadReadyV2Batch(List batchData) async { - if (batchData.isEmpty) return; + if (batchData.isEmpty || isCancelled) { + return; + } _logger.info('Processing batch of ${batchData.length} AssetUploadReadyV2 events'); @@ -404,6 +413,9 @@ class SyncStreamService { } Future handleWsAssetEditReadyV1(dynamic data) async { + if (isCancelled) { + return; + } _logger.info('Processing AssetEditReadyV1 event'); try { @@ -444,6 +456,9 @@ class SyncStreamService { } Future handleWsAssetEditReadyV2(dynamic data) async { + if (isCancelled) { + return; + } _logger.info('Processing AssetEditReadyV2 event'); try { @@ -492,22 +507,22 @@ class SyncStreamService { } Future _trashLocalAssets(Map> localAssetsToTrash) async { - final mediaUrls = await Future.wait( - localAssetsToTrash.values - .expand((e) => e) - .map((localAsset) => _storageRepository.getAssetEntityForAsset(localAsset).then((e) => e?.getMediaUrl())), - ); - _logger.info("Moving to trash ${mediaUrls.join(", ")} assets"); - final result = await _localFilesManager.moveToTrash(mediaUrls.nonNulls.toList()); - if (result) { - await _trashedLocalAssetRepository.trashLocalAsset(localAssetsToTrash); + final localIds = localAssetsToTrash.values.expand((assets) => assets).map((asset) => asset.id).toList(); + _logger.info("Moving to trash ${localIds.join(", ")} assets"); + final movedIds = await _assetMediaRepository.deleteAll(localIds); + if (movedIds.isNotEmpty) { + final movedAssetsByAlbum = localAssetsToTrash.map( + (albumId, assets) => MapEntry(albumId, assets.where((asset) => movedIds.contains(asset.id)).toList()), + )..removeWhere((_, assets) => assets.isEmpty); + + await _trashedLocalAssetRepository.trashLocalAsset(movedAssetsByAlbum); } } Future _applyRemoteRestoreToLocal() async { final assetsToRestore = await _trashedLocalAssetRepository.getToRestore(); if (assetsToRestore.isNotEmpty) { - final restoredIds = await _localFilesManager.restoreAssetsFromTrash(assetsToRestore); + final restoredIds = await _assetMediaRepository.restoreAssetsFromTrash(assetsToRestore); await _trashedLocalAssetRepository.applyRestoredAssets(restoredIds); } else { _logger.info("No remote assets found for restoration"); @@ -515,7 +530,7 @@ class SyncStreamService { } Future _syncAssetTrashStatus(List remoteIds) async { - if (!(await _localFilesManager.hasManageMediaPermission())) { + if (!(await _permissionRepository.hasManageMediaPermission())) { _logger.warning("Syncing asset trash status cannot proceed because MANAGE_MEDIA permission is missing"); return; } @@ -525,7 +540,7 @@ class SyncStreamService { } Future _syncAssetDeletion(List remoteIds) async { - if (!(await _localFilesManager.hasManageMediaPermission())) { + if (!(await _permissionRepository.hasManageMediaPermission())) { _logger.warning("Syncing asset deletion cannot proceed because MANAGE_MEDIA permission is missing"); return; } diff --git a/mobile/lib/domain/services/tag.service.dart b/mobile/lib/domain/services/tag.service.dart new file mode 100644 index 0000000000..6eeb83d4cb --- /dev/null +++ b/mobile/lib/domain/services/tag.service.dart @@ -0,0 +1,31 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/tag.model.dart'; +import 'package:immich_mobile/infrastructure/repositories/tags_api.repository.dart'; + +final tagServiceProvider = Provider((ref) => TagService(ref.watch(tagsApiRepositoryProvider))); + +class TagService { + final TagsApiRepository _repository; + + const TagService(this._repository); + + Future bulkTagAssets(List assetIds, List tagIds) async { + return _repository.bulkTagAssets(assetIds, tagIds); + } + + Future> getAllTags() async { + final dtos = await _repository.getAllTags(); + if (dtos == null) { + return {}; + } + return dtos.map((dto) => Tag.fromDto(dto)).toSet(); + } + + Future> upsertTags(List tags) async { + final dtos = await _repository.upsertTags(tags); + if (dtos == null) { + return []; + } + return dtos.map((dto) => Tag.fromDto(dto)).toList(); + } +} diff --git a/mobile/lib/domain/services/timeline.service.dart b/mobile/lib/domain/services/timeline.service.dart index a055f8bcae..4cc58b0fe7 100644 --- a/mobile/lib/domain/services/timeline.service.dart +++ b/mobile/lib/domain/services/timeline.service.dart @@ -5,10 +5,9 @@ import 'package:collection/collection.dart'; import 'package:immich_mobile/constants/constants.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/events.model.dart'; -import 'package:immich_mobile/domain/models/setting.model.dart'; import 'package:immich_mobile/domain/models/timeline.model.dart'; -import 'package:immich_mobile/domain/services/setting.service.dart'; import 'package:immich_mobile/domain/utils/event_stream.dart'; +import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/timeline.repository.dart'; import 'package:immich_mobile/utils/async_mutex.dart'; @@ -35,18 +34,17 @@ enum TimelineOrigin { deepLink, albumActivities, folder, + recentlyAdded, } class TimelineFactory { final DriftTimelineRepository _timelineRepository; - final SettingsService _settingsService; + final SettingsRepository _settingsRepository; - const TimelineFactory({required DriftTimelineRepository timelineRepository, required SettingsService settingsService}) - : _timelineRepository = timelineRepository, - _settingsService = settingsService; + const TimelineFactory({required this._timelineRepository, required this._settingsRepository}); GroupAssetsBy get groupBy { - final group = GroupAssetsBy.values[_settingsService.get(Setting.groupAssetsBy)]; + final group = _settingsRepository.appConfig.timeline.groupAssetsBy; // We do not support auto grouping in the new timeline yet, fallback to day grouping return group == GroupAssetsBy.auto ? GroupAssetsBy.day : group; } @@ -61,6 +59,8 @@ class TimelineFactory { TimelineService remoteAssets(String userId) => TimelineService(_timelineRepository.remote(userId, groupBy)); + TimelineService recentlyAdded(String userId) => TimelineService(_timelineRepository.recentlyAdded(userId, groupBy)); + TimelineService favorite(String userId) => TimelineService(_timelineRepository.favorite(userId, groupBy)); TimelineService trash(String userId) => TimelineService(_timelineRepository.trash(userId, groupBy)); @@ -104,12 +104,7 @@ class TimelineService { TimelineService(TimelineQuery query) : this._(assetSource: query.assetSource, bucketSource: query.bucketSource, origin: query.origin); - TimelineService._({ - required TimelineAssetSource assetSource, - required TimelineBucketSource bucketSource, - required this.origin, - }) : _assetSource = assetSource, - _bucketSource = bucketSource { + TimelineService._({required this._assetSource, required this._bucketSource, required this.origin}) { _bucketSubscription = _bucketSource().listen((buckets) { _mutex.run(() async { final totalAssets = buckets.fold(0, (acc, bucket) => acc + bucket.assetCount); diff --git a/mobile/lib/domain/services/user.service.dart b/mobile/lib/domain/services/user.service.dart index 1f9c015ad7..d5c1a6c4f7 100644 --- a/mobile/lib/domain/services/user.service.dart +++ b/mobile/lib/domain/services/user.service.dart @@ -12,9 +12,7 @@ class UserService { final UserApiRepository _userApiRepository; final StoreService _storeService; - UserService({required UserApiRepository userApiRepository, required StoreService storeService}) - : _userApiRepository = userApiRepository, - _storeService = storeService; + UserService({required this._userApiRepository, required this._storeService}); UserDto getMyUser() { return _storeService.get(StoreKey.currentUser); @@ -30,7 +28,9 @@ class UserService { Future refreshMyUser() async { final user = await _userApiRepository.getMyUser(); - if (user == null) return null; + if (user == null) { + return null; + } await _storeService.put(StoreKey.currentUser, user); return user; } diff --git a/mobile/lib/domain/utils/background_sync.dart b/mobile/lib/domain/utils/background_sync.dart index 030e77cd54..82f397d9b6 100644 --- a/mobile/lib/domain/utils/background_sync.dart +++ b/mobile/lib/domain/utils/background_sync.dart @@ -50,53 +50,27 @@ class BackgroundSyncManager { }); Future cancel() async { - final futures = []; - - if (_syncTask != null) { - futures.add(_syncTask!.future); + final tasks = [ + _syncTask, + _syncWebsocketTask, + _cloudIdSyncTask, + _linkedAlbumSyncTask, + _deviceAlbumSyncTask, + _hashTask, + ]; + final futures = [ + for (final task in tasks) + if (task != null) task.future, + ]; + for (final task in tasks) { + task?.cancel(); } - _syncTask?.cancel(); _syncTask = null; - - if (_syncWebsocketTask != null) { - futures.add(_syncWebsocketTask!.future); - } - _syncWebsocketTask?.cancel(); _syncWebsocketTask = null; - - if (_cloudIdSyncTask != null) { - futures.add(_cloudIdSyncTask!.future); - } - _cloudIdSyncTask?.cancel(); _cloudIdSyncTask = null; - - if (_linkedAlbumSyncTask != null) { - futures.add(_linkedAlbumSyncTask!.future); - } - _linkedAlbumSyncTask?.cancel(); _linkedAlbumSyncTask = null; - - try { - await Future.wait(futures); - } on CanceledError { - // Ignore cancellation errors - } - } - - Future cancelLocal() async { - final futures = []; - - if (_hashTask != null) { - futures.add(_hashTask!.future); - } - _hashTask?.cancel(); - _hashTask = null; - - if (_deviceAlbumSyncTask != null) { - futures.add(_deviceAlbumSyncTask!.future); - } - _deviceAlbumSyncTask?.cancel(); _deviceAlbumSyncTask = null; + _hashTask = null; try { await Future.wait(futures); diff --git a/mobile/lib/domain/utils/migrate_cloud_ids.dart b/mobile/lib/domain/utils/migrate_cloud_ids.dart index 32188b4838..efef6e8327 100644 --- a/mobile/lib/domain/utils/migrate_cloud_ids.dart +++ b/mobile/lib/domain/utils/migrate_cloud_ids.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:drift/drift.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/constants.dart'; @@ -9,6 +11,7 @@ import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart'; import 'package:immich_mobile/platform/native_sync_api.g.dart'; import 'package:immich_mobile/providers/api.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/cancel.provider.dart'; import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; import 'package:immich_mobile/providers/infrastructure/sync.provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; @@ -51,9 +54,10 @@ Future syncCloudIds(ProviderContainer ref) async { } final assetApi = ref.read(apiServiceProvider).assetsApi; + final cancellation = ref.read(cancellationProvider); // Process cloud IDs in paginated batches - await _processCloudIdMappingsInBatches(db, currentUser.id, assetApi, canBulkUpdateMetadata, logger); + await _processCloudIdMappingsInBatches(db, currentUser.id, assetApi, canBulkUpdateMetadata, logger, cancellation); } Future _processCloudIdMappingsInBatches( @@ -62,12 +66,17 @@ Future _processCloudIdMappingsInBatches( AssetsApi assetsApi, bool canBulkUpdate, Logger logger, + Completer cancellation, ) async { const pageSize = 20000; String? lastLocalId; final seenRemoteAssetIds = {}; while (true) { + if (cancellation.isCompleted) { + logger.warning('Cloud ID migration cancelled. Stopping batch processing.'); + break; + } final mappings = await _fetchCloudIdMappings(drift, userId, pageSize, lastLocalId); if (mappings.isEmpty) { break; @@ -98,9 +107,9 @@ Future _processCloudIdMappingsInBatches( if (items.isNotEmpty) { if (canBulkUpdate) { - await _bulkUpdateCloudIds(assetsApi, items); + await _bulkUpdateCloudIds(assetsApi, items, cancellation.future); } else { - await _sequentialUpdateCloudIds(assetsApi, items); + await _sequentialUpdateCloudIds(assetsApi, items, cancellation); } } @@ -111,20 +120,35 @@ Future _processCloudIdMappingsInBatches( } } -Future _sequentialUpdateCloudIds(AssetsApi assetsApi, List items) async { +Future _sequentialUpdateCloudIds( + AssetsApi assetsApi, + List items, + Completer cancellation, +) async { for (final item in items) { + if (cancellation.isCompleted) { + break; + } final upsertItem = AssetMetadataUpsertItemDto(key: item.key, value: item.value); try { - await assetsApi.updateAssetMetadata(item.assetId, AssetMetadataUpsertDto(items: [upsertItem])); + await assetsApi.updateAssetMetadata( + item.assetId, + AssetMetadataUpsertDto(items: [upsertItem]), + abortTrigger: cancellation.future, + ); } catch (error, stack) { Logger('migrateCloudIds').warning('Failed to update metadata for asset ${item.assetId}', error, stack); } } } -Future _bulkUpdateCloudIds(AssetsApi assetsApi, List items) async { +Future _bulkUpdateCloudIds( + AssetsApi assetsApi, + List items, + Future abortTrigger, +) async { try { - await assetsApi.updateBulkAssetMetadata(AssetMetadataBulkUpsertDto(items: items)); + await assetsApi.updateBulkAssetMetadata(AssetMetadataBulkUpsertDto(items: items), abortTrigger: abortTrigger); } catch (error, stack) { Logger('migrateCloudIds').warning('Failed to bulk update metadata', error, stack); } diff --git a/mobile/lib/extensions/asset_extensions.dart b/mobile/lib/extensions/asset_extensions.dart index 3a994f9cb8..445af99e79 100644 --- a/mobile/lib/extensions/asset_extensions.dart +++ b/mobile/lib/extensions/asset_extensions.dart @@ -11,17 +11,18 @@ extension DTOToAsset on api.AssetResponseDto { checksum: checksum, createdAt: fileCreatedAt, updatedAt: updatedAt, + uploadedAt: createdAt, ownerId: ownerId, visibility: visibility.toAssetVisibility(), durationMs: duration, height: height?.toInt(), width: width?.toInt(), isFavorite: isFavorite, - livePhotoVideoId: livePhotoVideoId, + livePhotoVideoId: livePhotoVideoId.orElse(null), thumbHash: thumbhash, localId: null, type: type.toAssetType(), - stackId: stack?.id, + stackId: stack.orElse(null)?.id, isEdited: isEdited, ); } @@ -33,19 +34,20 @@ extension DTOToAsset on api.AssetResponseDto { checksum: checksum, createdAt: fileCreatedAt, updatedAt: updatedAt, + uploadedAt: createdAt, ownerId: ownerId, visibility: visibility.toAssetVisibility(), durationMs: duration, height: height?.toInt(), width: width?.toInt(), isFavorite: isFavorite, - livePhotoVideoId: livePhotoVideoId, + livePhotoVideoId: livePhotoVideoId.orElse(null), thumbHash: thumbhash, localId: null, type: type.toAssetType(), - stackId: stack?.id, + stackId: stack.orElse(null)?.id, isEdited: isEdited, - exifInfo: exifInfo != null ? ExifDtoConverter.fromDto(exifInfo!) : const ExifInfo(), + exifInfo: exifInfo.orElse(null) != null ? ExifDtoConverter.fromDto(exifInfo.orElse(null)!) : const ExifInfo(), ); } } diff --git a/mobile/lib/extensions/scroll_extensions.dart b/mobile/lib/extensions/scroll_extensions.dart index 5b8f9e2a13..eb11734c67 100644 --- a/mobile/lib/extensions/scroll_extensions.dart +++ b/mobile/lib/extensions/scroll_extensions.dart @@ -94,8 +94,12 @@ class SnapScrollPhysics extends ScrollPhysics { bool get allowUserScrolling => false; static double target(ScrollMetrics position, double velocity, double snapOffset) { - if (velocity > _minFlingVelocity) return snapOffset; - if (velocity < -_minFlingVelocity) return position.pixels < snapOffset ? 0.0 : snapOffset; + if (velocity > _minFlingVelocity) { + return snapOffset; + } + if (velocity < -_minFlingVelocity) { + return position.pixels < snapOffset ? 0.0 : snapOffset; + } return position.pixels < minSnapDistance ? 0.0 : snapOffset; } } diff --git a/mobile/lib/extensions/string_extensions.dart b/mobile/lib/extensions/string_extensions.dart index d30c221f96..792b932df2 100644 --- a/mobile/lib/extensions/string_extensions.dart +++ b/mobile/lib/extensions/string_extensions.dart @@ -4,6 +4,8 @@ extension StringExtension on String { String capitalize() { return split(" ").map((str) => str.isEmpty ? str : str[0].toUpperCase() + str.substring(1)).join(" "); } + + String? get nullIfEmpty => isEmpty ? null : this; } extension DurationExtension on String { diff --git a/mobile/lib/infrastructure/entities/asset_face.entity.dart b/mobile/lib/infrastructure/entities/asset_face.entity.dart index 40fe9ab1c1..b94a0cf094 100644 --- a/mobile/lib/infrastructure/entities/asset_face.entity.dart +++ b/mobile/lib/infrastructure/entities/asset_face.entity.dart @@ -5,6 +5,11 @@ import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart'; @TableIndex.sql('CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)') @TableIndex.sql('CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)') +@TableIndex.sql(''' +CREATE INDEX IF NOT EXISTS idx_asset_face_visible_person +ON asset_face_entity (person_id, asset_id) +WHERE is_visible = 1 AND deleted_at IS NULL +''') class AssetFaceEntity extends Table with DriftDefaultsMixin { const AssetFaceEntity(); diff --git a/mobile/lib/infrastructure/entities/asset_face.entity.drift.dart b/mobile/lib/infrastructure/entities/asset_face.entity.drift.dart index c97dd545a8..d262325742 100644 --- a/mobile/lib/infrastructure/entities/asset_face.entity.drift.dart +++ b/mobile/lib/infrastructure/entities/asset_face.entity.drift.dart @@ -1350,3 +1350,7 @@ i0.Index get idxAssetFaceAssetId => i0.Index( 'idx_asset_face_asset_id', 'CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)', ); +i0.Index get idxAssetFaceVisiblePerson => i0.Index( + 'idx_asset_face_visible_person', + 'CREATE INDEX IF NOT EXISTS idx_asset_face_visible_person ON asset_face_entity (person_id, asset_id) WHERE is_visible = 1 AND deleted_at IS NULL', +); diff --git a/mobile/lib/infrastructure/entities/asset_ocr.entity.drift.dart b/mobile/lib/infrastructure/entities/asset_ocr.entity.drift.dart index 390fe28c65..5d6d8787de 100644 --- a/mobile/lib/infrastructure/entities/asset_ocr.entity.drift.dart +++ b/mobile/lib/infrastructure/entities/asset_ocr.entity.drift.dart @@ -536,6 +536,10 @@ typedef $$AssetOcrEntityTableProcessedTableManager = i1.AssetOcrEntityData, i0.PrefetchHooks Function({bool assetId}) >; +i0.Index get idxAssetOcrAssetId => i0.Index( + 'idx_asset_ocr_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_ocr_asset_id ON asset_ocr_entity (asset_id)', +); class $AssetOcrEntityTable extends i2.AssetOcrEntity with i0.TableInfo<$AssetOcrEntityTable, i1.AssetOcrEntityData> { diff --git a/mobile/lib/infrastructure/entities/exif.entity.dart b/mobile/lib/infrastructure/entities/exif.entity.dart index e009029ea7..120fbd0c68 100644 --- a/mobile/lib/infrastructure/entities/exif.entity.dart +++ b/mobile/lib/infrastructure/entities/exif.entity.dart @@ -6,6 +6,10 @@ import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart'; import 'package:immich_mobile/infrastructure/utils/exif.converter.dart'; @TableIndex.sql('CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)') +@TableIndex.sql(''' +CREATE INDEX IF NOT EXISTS idx_remote_exif_city +ON remote_exif_entity (city) WHERE city IS NOT NULL +''') class RemoteExifEntity extends Table with DriftDefaultsMixin { const RemoteExifEntity(); diff --git a/mobile/lib/infrastructure/entities/exif.entity.drift.dart b/mobile/lib/infrastructure/entities/exif.entity.drift.dart index 8695e2004b..cbe31f5bb4 100644 --- a/mobile/lib/infrastructure/entities/exif.entity.drift.dart +++ b/mobile/lib/infrastructure/entities/exif.entity.drift.dart @@ -1883,3 +1883,8 @@ class RemoteExifEntityCompanion .toString(); } } + +i0.Index get idxRemoteExifCity => i0.Index( + 'idx_remote_exif_city', + 'CREATE INDEX IF NOT EXISTS idx_remote_exif_city ON remote_exif_entity (city) WHERE city IS NOT NULL', +); diff --git a/mobile/lib/infrastructure/entities/local_asset.entity.dart b/mobile/lib/infrastructure/entities/local_asset.entity.dart index 5a14a44fb7..a19c1aa540 100644 --- a/mobile/lib/infrastructure/entities/local_asset.entity.dart +++ b/mobile/lib/infrastructure/entities/local_asset.entity.dart @@ -6,6 +6,7 @@ import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart'; @TableIndex.sql('CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)') @TableIndex.sql('CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)') +@TableIndex.sql('CREATE INDEX IF NOT EXISTS idx_local_asset_created_at ON local_asset_entity (created_at)') class LocalAssetEntity extends Table with DriftDefaultsMixin, AssetEntityMixin { const LocalAssetEntity(); diff --git a/mobile/lib/infrastructure/entities/local_asset.entity.drift.dart b/mobile/lib/infrastructure/entities/local_asset.entity.drift.dart index e01e6ce745..fe03f9b208 100644 --- a/mobile/lib/infrastructure/entities/local_asset.entity.drift.dart +++ b/mobile/lib/infrastructure/entities/local_asset.entity.drift.dart @@ -1348,3 +1348,7 @@ i0.Index get idxLocalAssetCloudId => i0.Index( 'idx_local_asset_cloud_id', 'CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)', ); +i0.Index get idxLocalAssetCreatedAt => i0.Index( + 'idx_local_asset_created_at', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_created_at ON local_asset_entity (created_at)', +); diff --git a/mobile/lib/infrastructure/entities/merged_asset.drift b/mobile/lib/infrastructure/entities/merged_asset.drift index daad02e2b3..d0321ab1ef 100644 --- a/mobile/lib/infrastructure/entities/merged_asset.drift +++ b/mobile/lib/infrastructure/entities/merged_asset.drift @@ -4,7 +4,7 @@ import 'local_asset.entity.dart'; import 'local_album.entity.dart'; import 'local_album_asset.entity.dart'; -mergedAsset: +mergedAsset: SELECT rae.id as remote_id, (SELECT lae.id FROM local_asset_entity lae WHERE lae.checksum = rae.checksum LIMIT 1) as local_id, @@ -27,7 +27,8 @@ SELECT NULL as longitude, NULL as adjustmentTime, rae.is_edited, - 0 as playback_style + 0 as playback_style, + rae.uploaded_at FROM remote_asset_entity rae LEFT JOIN @@ -65,7 +66,8 @@ SELECT lae.longitude, lae.adjustment_time, 0 as is_edited, - lae.playback_style + lae.playback_style, + NULL as uploaded_at FROM local_asset_entity lae WHERE NOT EXISTS ( diff --git a/mobile/lib/infrastructure/entities/merged_asset.drift.dart b/mobile/lib/infrastructure/entities/merged_asset.drift.dart index 1e501d4028..2d05ef6ceb 100644 --- a/mobile/lib/infrastructure/entities/merged_asset.drift.dart +++ b/mobile/lib/infrastructure/entities/merged_asset.drift.dart @@ -29,7 +29,7 @@ class MergedAssetDrift extends i1.ModularAccessor { ); $arrayStartIndex += generatedlimit.amountOfVariables; return customSelect( - 'SELECT rae.id AS remote_id, (SELECT lae.id FROM local_asset_entity AS lae WHERE lae.checksum = rae.checksum LIMIT 1) AS local_id, rae.name, rae.type, rae.created_at AS created_at, rae.updated_at, rae.width, rae.height, rae.duration_ms, rae.is_favorite, rae.thumb_hash, rae.checksum, rae.owner_id, rae.live_photo_video_id, 0 AS orientation, rae.stack_id, NULL AS i_cloud_id, NULL AS latitude, NULL AS longitude, NULL AS adjustmentTime, rae.is_edited, 0 AS playback_style FROM remote_asset_entity AS rae LEFT JOIN stack_entity AS se ON rae.stack_id = se.id WHERE rae.deleted_at IS NULL AND rae.visibility = 0 AND rae.owner_id IN ($expandeduserIds) AND(rae.stack_id IS NULL OR rae.id = se.primary_asset_id)UNION ALL SELECT NULL AS remote_id, lae.id AS local_id, lae.name, lae.type, lae.created_at AS created_at, lae.updated_at, lae.width, lae.height, lae.duration_ms, lae.is_favorite, NULL AS thumb_hash, lae.checksum, NULL AS owner_id, NULL AS live_photo_video_id, lae.orientation, NULL AS stack_id, lae.i_cloud_id, lae.latitude, lae.longitude, lae.adjustment_time, 0 AS is_edited, lae.playback_style FROM local_asset_entity AS lae WHERE NOT EXISTS (SELECT 1 FROM remote_asset_entity AS rae WHERE rae.checksum = lae.checksum AND rae.owner_id IN ($expandeduserIds)) AND EXISTS (SELECT 1 FROM local_album_asset_entity AS laa INNER JOIN local_album_entity AS la ON laa.album_id = la.id WHERE laa.asset_id = lae.id AND la.backup_selection = 0) AND NOT EXISTS (SELECT 1 FROM local_album_asset_entity AS laa INNER JOIN local_album_entity AS la ON laa.album_id = la.id WHERE laa.asset_id = lae.id AND la.backup_selection = 2) ORDER BY created_at DESC ${generatedlimit.sql}', + 'SELECT rae.id AS remote_id, (SELECT lae.id FROM local_asset_entity AS lae WHERE lae.checksum = rae.checksum LIMIT 1) AS local_id, rae.name, rae.type, rae.created_at AS created_at, rae.updated_at, rae.width, rae.height, rae.duration_ms, rae.is_favorite, rae.thumb_hash, rae.checksum, rae.owner_id, rae.live_photo_video_id, 0 AS orientation, rae.stack_id, NULL AS i_cloud_id, NULL AS latitude, NULL AS longitude, NULL AS adjustmentTime, rae.is_edited, 0 AS playback_style, rae.uploaded_at FROM remote_asset_entity AS rae LEFT JOIN stack_entity AS se ON rae.stack_id = se.id WHERE rae.deleted_at IS NULL AND rae.visibility = 0 AND rae.owner_id IN ($expandeduserIds) AND(rae.stack_id IS NULL OR rae.id = se.primary_asset_id)UNION ALL SELECT NULL AS remote_id, lae.id AS local_id, lae.name, lae.type, lae.created_at AS created_at, lae.updated_at, lae.width, lae.height, lae.duration_ms, lae.is_favorite, NULL AS thumb_hash, lae.checksum, NULL AS owner_id, NULL AS live_photo_video_id, lae.orientation, NULL AS stack_id, lae.i_cloud_id, lae.latitude, lae.longitude, lae.adjustment_time, 0 AS is_edited, lae.playback_style, NULL AS uploaded_at FROM local_asset_entity AS lae WHERE NOT EXISTS (SELECT 1 FROM remote_asset_entity AS rae WHERE rae.checksum = lae.checksum AND rae.owner_id IN ($expandeduserIds)) AND EXISTS (SELECT 1 FROM local_album_asset_entity AS laa INNER JOIN local_album_entity AS la ON laa.album_id = la.id WHERE laa.asset_id = lae.id AND la.backup_selection = 0) AND NOT EXISTS (SELECT 1 FROM local_album_asset_entity AS laa INNER JOIN local_album_entity AS la ON laa.album_id = la.id WHERE laa.asset_id = lae.id AND la.backup_selection = 2) ORDER BY created_at DESC ${generatedlimit.sql}', variables: [ for (var $ in userIds) i0.Variable($), ...generatedlimit.introducedVariables, @@ -68,6 +68,7 @@ class MergedAssetDrift extends i1.ModularAccessor { adjustmentTime: row.readNullable('adjustmentTime'), isEdited: row.read('is_edited'), playbackStyle: row.read('playback_style'), + uploadedAt: row.readNullable('uploaded_at'), ), ); } @@ -141,6 +142,7 @@ class MergedAssetResult { final DateTime? adjustmentTime; final bool isEdited; final int playbackStyle; + final DateTime? uploadedAt; MergedAssetResult({ this.remoteId, this.localId, @@ -164,6 +166,7 @@ class MergedAssetResult { this.adjustmentTime, required this.isEdited, required this.playbackStyle, + this.uploadedAt, }); } diff --git a/mobile/lib/infrastructure/entities/remote_asset.entity.dart b/mobile/lib/infrastructure/entities/remote_asset.entity.dart index 7cd8913542..ad1cec5641 100644 --- a/mobile/lib/infrastructure/entities/remote_asset.entity.dart +++ b/mobile/lib/infrastructure/entities/remote_asset.entity.dart @@ -5,9 +5,6 @@ import 'package:immich_mobile/infrastructure/entities/user.entity.dart'; import 'package:immich_mobile/infrastructure/utils/asset.mixin.dart'; import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart'; -@TableIndex.sql( - 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)', -) @TableIndex.sql(''' CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) @@ -20,12 +17,10 @@ WHERE (library_id IS NOT NULL); ''') @TableIndex.sql('CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)') @TableIndex.sql('CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)') -@TableIndex.sql( - "CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_day ON remote_asset_entity (STRFTIME('%Y-%m-%d', local_date_time))", -) -@TableIndex.sql( - "CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_month ON remote_asset_entity (STRFTIME('%Y-%m', local_date_time))", -) +@TableIndex.sql(''' +CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_visibility_deleted_created +ON remote_asset_entity (owner_id, visibility, deleted_at, created_at DESC) +''') class RemoteAssetEntity extends Table with DriftDefaultsMixin, AssetEntityMixin { const RemoteAssetEntity(); @@ -43,6 +38,8 @@ class RemoteAssetEntity extends Table with DriftDefaultsMixin, AssetEntityMixin DateTimeColumn get deletedAt => dateTime().nullable()(); + DateTimeColumn get uploadedAt => dateTime().nullable()(); + TextColumn get livePhotoVideoId => text().nullable()(); IntColumn get visibility => intEnum()(); @@ -66,6 +63,7 @@ extension RemoteAssetEntityDataDomainEx on RemoteAssetEntityData { type: type, createdAt: createdAt, updatedAt: updatedAt, + uploadedAt: uploadedAt, durationMs: durationMs, isFavorite: isFavorite, height: height, @@ -76,5 +74,6 @@ extension RemoteAssetEntityDataDomainEx on RemoteAssetEntityData { localId: localId, stackId: stackId, isEdited: isEdited, + deletedAt: deletedAt, ); } diff --git a/mobile/lib/infrastructure/entities/remote_asset.entity.drift.dart b/mobile/lib/infrastructure/entities/remote_asset.entity.drift.dart index 845c99e3c2..8141573343 100644 --- a/mobile/lib/infrastructure/entities/remote_asset.entity.drift.dart +++ b/mobile/lib/infrastructure/entities/remote_asset.entity.drift.dart @@ -27,6 +27,7 @@ typedef $$RemoteAssetEntityTableCreateCompanionBuilder = i0.Value localDateTime, i0.Value thumbHash, i0.Value deletedAt, + i0.Value uploadedAt, i0.Value livePhotoVideoId, required i2.AssetVisibility visibility, i0.Value stackId, @@ -49,6 +50,7 @@ typedef $$RemoteAssetEntityTableUpdateCompanionBuilder = i0.Value localDateTime, i0.Value thumbHash, i0.Value deletedAt, + i0.Value uploadedAt, i0.Value livePhotoVideoId, i0.Value visibility, i0.Value stackId, @@ -177,6 +179,11 @@ class $$RemoteAssetEntityTableFilterComposer builder: (column) => i0.ColumnFilters(column), ); + i0.ColumnFilters get uploadedAt => $composableBuilder( + column: $table.uploadedAt, + builder: (column) => i0.ColumnFilters(column), + ); + i0.ColumnFilters get livePhotoVideoId => $composableBuilder( column: $table.livePhotoVideoId, builder: (column) => i0.ColumnFilters(column), @@ -305,6 +312,11 @@ class $$RemoteAssetEntityTableOrderingComposer builder: (column) => i0.ColumnOrderings(column), ); + i0.ColumnOrderings get uploadedAt => $composableBuilder( + column: $table.uploadedAt, + builder: (column) => i0.ColumnOrderings(column), + ); + i0.ColumnOrderings get livePhotoVideoId => $composableBuilder( column: $table.livePhotoVideoId, builder: (column) => i0.ColumnOrderings(column), @@ -412,6 +424,11 @@ class $$RemoteAssetEntityTableAnnotationComposer i0.GeneratedColumn get deletedAt => $composableBuilder(column: $table.deletedAt, builder: (column) => column); + i0.GeneratedColumn get uploadedAt => $composableBuilder( + column: $table.uploadedAt, + builder: (column) => column, + ); + i0.GeneratedColumn get livePhotoVideoId => $composableBuilder( column: $table.livePhotoVideoId, builder: (column) => column, @@ -507,6 +524,7 @@ class $$RemoteAssetEntityTableTableManager i0.Value localDateTime = const i0.Value.absent(), i0.Value thumbHash = const i0.Value.absent(), i0.Value deletedAt = const i0.Value.absent(), + i0.Value uploadedAt = const i0.Value.absent(), i0.Value livePhotoVideoId = const i0.Value.absent(), i0.Value visibility = const i0.Value.absent(), @@ -528,6 +546,7 @@ class $$RemoteAssetEntityTableTableManager localDateTime: localDateTime, thumbHash: thumbHash, deletedAt: deletedAt, + uploadedAt: uploadedAt, livePhotoVideoId: livePhotoVideoId, visibility: visibility, stackId: stackId, @@ -550,6 +569,7 @@ class $$RemoteAssetEntityTableTableManager i0.Value localDateTime = const i0.Value.absent(), i0.Value thumbHash = const i0.Value.absent(), i0.Value deletedAt = const i0.Value.absent(), + i0.Value uploadedAt = const i0.Value.absent(), i0.Value livePhotoVideoId = const i0.Value.absent(), required i2.AssetVisibility visibility, i0.Value stackId = const i0.Value.absent(), @@ -570,6 +590,7 @@ class $$RemoteAssetEntityTableTableManager localDateTime: localDateTime, thumbHash: thumbHash, deletedAt: deletedAt, + uploadedAt: uploadedAt, livePhotoVideoId: livePhotoVideoId, visibility: visibility, stackId: stackId, @@ -645,9 +666,9 @@ typedef $$RemoteAssetEntityTableProcessedTableManager = i1.RemoteAssetEntityData, i0.PrefetchHooks Function({bool ownerId}) >; -i0.Index get idxRemoteAssetOwnerChecksum => i0.Index( - 'idx_remote_asset_owner_checksum', - 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)', +i0.Index get uQRemoteAssetsOwnerChecksum => i0.Index( + 'UQ_remote_assets_owner_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)', ); class $RemoteAssetEntityTable extends i3.RemoteAssetEntity @@ -818,6 +839,18 @@ class $RemoteAssetEntityTable extends i3.RemoteAssetEntity type: i0.DriftSqlType.dateTime, requiredDuringInsert: false, ); + static const i0.VerificationMeta _uploadedAtMeta = const i0.VerificationMeta( + 'uploadedAt', + ); + @override + late final i0.GeneratedColumn uploadedAt = + i0.GeneratedColumn( + 'uploaded_at', + aliasedName, + true, + type: i0.DriftSqlType.dateTime, + requiredDuringInsert: false, + ); static const i0.VerificationMeta _livePhotoVideoIdMeta = const i0.VerificationMeta('livePhotoVideoId'); @override @@ -894,6 +927,7 @@ class $RemoteAssetEntityTable extends i3.RemoteAssetEntity localDateTime, thumbHash, deletedAt, + uploadedAt, livePhotoVideoId, visibility, stackId, @@ -998,6 +1032,12 @@ class $RemoteAssetEntityTable extends i3.RemoteAssetEntity deletedAt.isAcceptableOrUnknown(data['deleted_at']!, _deletedAtMeta), ); } + if (data.containsKey('uploaded_at')) { + context.handle( + _uploadedAtMeta, + uploadedAt.isAcceptableOrUnknown(data['uploaded_at']!, _uploadedAtMeta), + ); + } if (data.containsKey('live_photo_video_id')) { context.handle( _livePhotoVideoIdMeta, @@ -1095,6 +1135,10 @@ class $RemoteAssetEntityTable extends i3.RemoteAssetEntity i0.DriftSqlType.dateTime, data['${effectivePrefix}deleted_at'], ), + uploadedAt: attachedDatabase.typeMapping.read( + i0.DriftSqlType.dateTime, + data['${effectivePrefix}uploaded_at'], + ), livePhotoVideoId: attachedDatabase.typeMapping.read( i0.DriftSqlType.string, data['${effectivePrefix}live_photo_video_id'], @@ -1153,6 +1197,7 @@ class RemoteAssetEntityData extends i0.DataClass final DateTime? localDateTime; final String? thumbHash; final DateTime? deletedAt; + final DateTime? uploadedAt; final String? livePhotoVideoId; final i2.AssetVisibility visibility; final String? stackId; @@ -1173,6 +1218,7 @@ class RemoteAssetEntityData extends i0.DataClass this.localDateTime, this.thumbHash, this.deletedAt, + this.uploadedAt, this.livePhotoVideoId, required this.visibility, this.stackId, @@ -1212,6 +1258,9 @@ class RemoteAssetEntityData extends i0.DataClass if (!nullToAbsent || deletedAt != null) { map['deleted_at'] = i0.Variable(deletedAt); } + if (!nullToAbsent || uploadedAt != null) { + map['uploaded_at'] = i0.Variable(uploadedAt); + } if (!nullToAbsent || livePhotoVideoId != null) { map['live_photo_video_id'] = i0.Variable(livePhotoVideoId); } @@ -1252,6 +1301,7 @@ class RemoteAssetEntityData extends i0.DataClass localDateTime: serializer.fromJson(json['localDateTime']), thumbHash: serializer.fromJson(json['thumbHash']), deletedAt: serializer.fromJson(json['deletedAt']), + uploadedAt: serializer.fromJson(json['uploadedAt']), livePhotoVideoId: serializer.fromJson(json['livePhotoVideoId']), visibility: i1.$RemoteAssetEntityTable.$convertervisibility.fromJson( serializer.fromJson(json['visibility']), @@ -1281,6 +1331,7 @@ class RemoteAssetEntityData extends i0.DataClass 'localDateTime': serializer.toJson(localDateTime), 'thumbHash': serializer.toJson(thumbHash), 'deletedAt': serializer.toJson(deletedAt), + 'uploadedAt': serializer.toJson(uploadedAt), 'livePhotoVideoId': serializer.toJson(livePhotoVideoId), 'visibility': serializer.toJson( i1.$RemoteAssetEntityTable.$convertervisibility.toJson(visibility), @@ -1306,6 +1357,7 @@ class RemoteAssetEntityData extends i0.DataClass i0.Value localDateTime = const i0.Value.absent(), i0.Value thumbHash = const i0.Value.absent(), i0.Value deletedAt = const i0.Value.absent(), + i0.Value uploadedAt = const i0.Value.absent(), i0.Value livePhotoVideoId = const i0.Value.absent(), i2.AssetVisibility? visibility, i0.Value stackId = const i0.Value.absent(), @@ -1328,6 +1380,7 @@ class RemoteAssetEntityData extends i0.DataClass : this.localDateTime, thumbHash: thumbHash.present ? thumbHash.value : this.thumbHash, deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + uploadedAt: uploadedAt.present ? uploadedAt.value : this.uploadedAt, livePhotoVideoId: livePhotoVideoId.present ? livePhotoVideoId.value : this.livePhotoVideoId, @@ -1358,6 +1411,9 @@ class RemoteAssetEntityData extends i0.DataClass : this.localDateTime, thumbHash: data.thumbHash.present ? data.thumbHash.value : this.thumbHash, deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + uploadedAt: data.uploadedAt.present + ? data.uploadedAt.value + : this.uploadedAt, livePhotoVideoId: data.livePhotoVideoId.present ? data.livePhotoVideoId.value : this.livePhotoVideoId, @@ -1387,6 +1443,7 @@ class RemoteAssetEntityData extends i0.DataClass ..write('localDateTime: $localDateTime, ') ..write('thumbHash: $thumbHash, ') ..write('deletedAt: $deletedAt, ') + ..write('uploadedAt: $uploadedAt, ') ..write('livePhotoVideoId: $livePhotoVideoId, ') ..write('visibility: $visibility, ') ..write('stackId: $stackId, ') @@ -1412,6 +1469,7 @@ class RemoteAssetEntityData extends i0.DataClass localDateTime, thumbHash, deletedAt, + uploadedAt, livePhotoVideoId, visibility, stackId, @@ -1436,6 +1494,7 @@ class RemoteAssetEntityData extends i0.DataClass other.localDateTime == this.localDateTime && other.thumbHash == this.thumbHash && other.deletedAt == this.deletedAt && + other.uploadedAt == this.uploadedAt && other.livePhotoVideoId == this.livePhotoVideoId && other.visibility == this.visibility && other.stackId == this.stackId && @@ -1459,6 +1518,7 @@ class RemoteAssetEntityCompanion final i0.Value localDateTime; final i0.Value thumbHash; final i0.Value deletedAt; + final i0.Value uploadedAt; final i0.Value livePhotoVideoId; final i0.Value visibility; final i0.Value stackId; @@ -1479,6 +1539,7 @@ class RemoteAssetEntityCompanion this.localDateTime = const i0.Value.absent(), this.thumbHash = const i0.Value.absent(), this.deletedAt = const i0.Value.absent(), + this.uploadedAt = const i0.Value.absent(), this.livePhotoVideoId = const i0.Value.absent(), this.visibility = const i0.Value.absent(), this.stackId = const i0.Value.absent(), @@ -1500,6 +1561,7 @@ class RemoteAssetEntityCompanion this.localDateTime = const i0.Value.absent(), this.thumbHash = const i0.Value.absent(), this.deletedAt = const i0.Value.absent(), + this.uploadedAt = const i0.Value.absent(), this.livePhotoVideoId = const i0.Value.absent(), required i2.AssetVisibility visibility, this.stackId = const i0.Value.absent(), @@ -1526,6 +1588,7 @@ class RemoteAssetEntityCompanion i0.Expression? localDateTime, i0.Expression? thumbHash, i0.Expression? deletedAt, + i0.Expression? uploadedAt, i0.Expression? livePhotoVideoId, i0.Expression? visibility, i0.Expression? stackId, @@ -1547,6 +1610,7 @@ class RemoteAssetEntityCompanion if (localDateTime != null) 'local_date_time': localDateTime, if (thumbHash != null) 'thumb_hash': thumbHash, if (deletedAt != null) 'deleted_at': deletedAt, + if (uploadedAt != null) 'uploaded_at': uploadedAt, if (livePhotoVideoId != null) 'live_photo_video_id': livePhotoVideoId, if (visibility != null) 'visibility': visibility, if (stackId != null) 'stack_id': stackId, @@ -1570,6 +1634,7 @@ class RemoteAssetEntityCompanion i0.Value? localDateTime, i0.Value? thumbHash, i0.Value? deletedAt, + i0.Value? uploadedAt, i0.Value? livePhotoVideoId, i0.Value? visibility, i0.Value? stackId, @@ -1591,6 +1656,7 @@ class RemoteAssetEntityCompanion localDateTime: localDateTime ?? this.localDateTime, thumbHash: thumbHash ?? this.thumbHash, deletedAt: deletedAt ?? this.deletedAt, + uploadedAt: uploadedAt ?? this.uploadedAt, livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId, visibility: visibility ?? this.visibility, stackId: stackId ?? this.stackId, @@ -1646,6 +1712,9 @@ class RemoteAssetEntityCompanion if (deletedAt.present) { map['deleted_at'] = i0.Variable(deletedAt.value); } + if (uploadedAt.present) { + map['uploaded_at'] = i0.Variable(uploadedAt.value); + } if (livePhotoVideoId.present) { map['live_photo_video_id'] = i0.Variable(livePhotoVideoId.value); } @@ -1683,6 +1752,7 @@ class RemoteAssetEntityCompanion ..write('localDateTime: $localDateTime, ') ..write('thumbHash: $thumbHash, ') ..write('deletedAt: $deletedAt, ') + ..write('uploadedAt: $uploadedAt, ') ..write('livePhotoVideoId: $livePhotoVideoId, ') ..write('visibility: $visibility, ') ..write('stackId: $stackId, ') @@ -1693,10 +1763,6 @@ class RemoteAssetEntityCompanion } } -i0.Index get uQRemoteAssetsOwnerChecksum => i0.Index( - 'UQ_remote_assets_owner_checksum', - 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)', -); i0.Index get uQRemoteAssetsOwnerLibraryChecksum => i0.Index( 'UQ_remote_assets_owner_library_checksum', 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)', @@ -1709,11 +1775,7 @@ i0.Index get idxRemoteAssetStackId => i0.Index( 'idx_remote_asset_stack_id', 'CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)', ); -i0.Index get idxRemoteAssetLocalDateTimeDay => i0.Index( - 'idx_remote_asset_local_date_time_day', - 'CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_day ON remote_asset_entity (STRFTIME(\'%Y-%m-%d\', local_date_time))', -); -i0.Index get idxRemoteAssetLocalDateTimeMonth => i0.Index( - 'idx_remote_asset_local_date_time_month', - 'CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_month ON remote_asset_entity (STRFTIME(\'%Y-%m\', local_date_time))', +i0.Index get idxRemoteAssetOwnerVisibilityDeletedCreated => i0.Index( + 'idx_remote_asset_owner_visibility_deleted_created', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_visibility_deleted_created ON remote_asset_entity (owner_id, visibility, deleted_at, created_at DESC)', ); diff --git a/mobile/lib/infrastructure/entities/settings.entity.dart b/mobile/lib/infrastructure/entities/settings.entity.dart new file mode 100644 index 0000000000..36e0bcc990 --- /dev/null +++ b/mobile/lib/infrastructure/entities/settings.entity.dart @@ -0,0 +1,18 @@ +import 'package:drift/drift.dart'; +import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart'; + +class SettingsEntity extends Table with DriftDefaultsMixin { + const SettingsEntity(); + + TextColumn get key => text()(); + + TextColumn get value => text()(); + + DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)(); + + @override + Set get primaryKey => {key}; + + @override + String get tableName => "settings"; +} diff --git a/mobile/lib/infrastructure/entities/settings.entity.drift.dart b/mobile/lib/infrastructure/entities/settings.entity.drift.dart new file mode 100644 index 0000000000..e2cac89a5e --- /dev/null +++ b/mobile/lib/infrastructure/entities/settings.entity.drift.dart @@ -0,0 +1,429 @@ +// dart format width=80 +// ignore_for_file: type=lint +import 'package:drift/drift.dart' as i0; +import 'package:immich_mobile/infrastructure/entities/settings.entity.drift.dart' + as i1; +import 'package:immich_mobile/infrastructure/entities/settings.entity.dart' + as i2; +import 'package:drift/src/runtime/query_builder/query_builder.dart' as i3; + +typedef $$SettingsEntityTableCreateCompanionBuilder = + i1.SettingsEntityCompanion Function({ + required String key, + required String value, + i0.Value updatedAt, + }); +typedef $$SettingsEntityTableUpdateCompanionBuilder = + i1.SettingsEntityCompanion Function({ + i0.Value key, + i0.Value value, + i0.Value updatedAt, + }); + +class $$SettingsEntityTableFilterComposer + extends i0.Composer { + $$SettingsEntityTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnFilters get key => $composableBuilder( + column: $table.key, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get value => $composableBuilder( + column: $table.value, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get updatedAt => $composableBuilder( + column: $table.updatedAt, + builder: (column) => i0.ColumnFilters(column), + ); +} + +class $$SettingsEntityTableOrderingComposer + extends i0.Composer { + $$SettingsEntityTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnOrderings get key => $composableBuilder( + column: $table.key, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get value => $composableBuilder( + column: $table.value, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get updatedAt => $composableBuilder( + column: $table.updatedAt, + builder: (column) => i0.ColumnOrderings(column), + ); +} + +class $$SettingsEntityTableAnnotationComposer + extends i0.Composer { + $$SettingsEntityTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.GeneratedColumn get key => + $composableBuilder(column: $table.key, builder: (column) => column); + + i0.GeneratedColumn get value => + $composableBuilder(column: $table.value, builder: (column) => column); + + i0.GeneratedColumn get updatedAt => + $composableBuilder(column: $table.updatedAt, builder: (column) => column); +} + +class $$SettingsEntityTableTableManager + extends + i0.RootTableManager< + i0.GeneratedDatabase, + i1.$SettingsEntityTable, + i1.SettingsEntityData, + i1.$$SettingsEntityTableFilterComposer, + i1.$$SettingsEntityTableOrderingComposer, + i1.$$SettingsEntityTableAnnotationComposer, + $$SettingsEntityTableCreateCompanionBuilder, + $$SettingsEntityTableUpdateCompanionBuilder, + ( + i1.SettingsEntityData, + i0.BaseReferences< + i0.GeneratedDatabase, + i1.$SettingsEntityTable, + i1.SettingsEntityData + >, + ), + i1.SettingsEntityData, + i0.PrefetchHooks Function() + > { + $$SettingsEntityTableTableManager( + i0.GeneratedDatabase db, + i1.$SettingsEntityTable table, + ) : super( + i0.TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + i1.$$SettingsEntityTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + i1.$$SettingsEntityTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => i1 + .$$SettingsEntityTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + i0.Value key = const i0.Value.absent(), + i0.Value value = const i0.Value.absent(), + i0.Value updatedAt = const i0.Value.absent(), + }) => i1.SettingsEntityCompanion( + key: key, + value: value, + updatedAt: updatedAt, + ), + createCompanionCallback: + ({ + required String key, + required String value, + i0.Value updatedAt = const i0.Value.absent(), + }) => i1.SettingsEntityCompanion.insert( + key: key, + value: value, + updatedAt: updatedAt, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), i0.BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + ), + ); +} + +typedef $$SettingsEntityTableProcessedTableManager = + i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.$SettingsEntityTable, + i1.SettingsEntityData, + i1.$$SettingsEntityTableFilterComposer, + i1.$$SettingsEntityTableOrderingComposer, + i1.$$SettingsEntityTableAnnotationComposer, + $$SettingsEntityTableCreateCompanionBuilder, + $$SettingsEntityTableUpdateCompanionBuilder, + ( + i1.SettingsEntityData, + i0.BaseReferences< + i0.GeneratedDatabase, + i1.$SettingsEntityTable, + i1.SettingsEntityData + >, + ), + i1.SettingsEntityData, + i0.PrefetchHooks Function() + >; + +class $SettingsEntityTable extends i2.SettingsEntity + with i0.TableInfo<$SettingsEntityTable, i1.SettingsEntityData> { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + $SettingsEntityTable(this.attachedDatabase, [this._alias]); + static const i0.VerificationMeta _keyMeta = const i0.VerificationMeta('key'); + @override + late final i0.GeneratedColumn key = i0.GeneratedColumn( + 'key', + aliasedName, + false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + ); + static const i0.VerificationMeta _valueMeta = const i0.VerificationMeta( + 'value', + ); + @override + late final i0.GeneratedColumn value = i0.GeneratedColumn( + 'value', + aliasedName, + false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + ); + static const i0.VerificationMeta _updatedAtMeta = const i0.VerificationMeta( + 'updatedAt', + ); + @override + late final i0.GeneratedColumn updatedAt = + i0.GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: i0.DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: i3.currentDateAndTime, + ); + @override + List get $columns => [key, value, updatedAt]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'settings'; + @override + i0.VerificationContext validateIntegrity( + i0.Insertable instance, { + bool isInserting = false, + }) { + final context = i0.VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('key')) { + context.handle( + _keyMeta, + key.isAcceptableOrUnknown(data['key']!, _keyMeta), + ); + } else if (isInserting) { + context.missing(_keyMeta); + } + if (data.containsKey('value')) { + context.handle( + _valueMeta, + value.isAcceptableOrUnknown(data['value']!, _valueMeta), + ); + } else if (isInserting) { + context.missing(_valueMeta); + } + if (data.containsKey('updated_at')) { + context.handle( + _updatedAtMeta, + updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta), + ); + } + return context; + } + + @override + Set get $primaryKey => {key}; + @override + i1.SettingsEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.SettingsEntityData( + key: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}key'], + )!, + value: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}value'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + i0.DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ); + } + + @override + $SettingsEntityTable createAlias(String alias) { + return $SettingsEntityTable(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class SettingsEntityData extends i0.DataClass + implements i0.Insertable { + final String key; + final String value; + final DateTime updatedAt; + const SettingsEntityData({ + required this.key, + required this.value, + required this.updatedAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['key'] = i0.Variable(key); + map['value'] = i0.Variable(value); + map['updated_at'] = i0.Variable(updatedAt); + return map; + } + + factory SettingsEntityData.fromJson( + Map json, { + i0.ValueSerializer? serializer, + }) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return SettingsEntityData( + key: serializer.fromJson(json['key']), + value: serializer.fromJson(json['value']), + updatedAt: serializer.fromJson(json['updatedAt']), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'key': serializer.toJson(key), + 'value': serializer.toJson(value), + 'updatedAt': serializer.toJson(updatedAt), + }; + } + + i1.SettingsEntityData copyWith({ + String? key, + String? value, + DateTime? updatedAt, + }) => i1.SettingsEntityData( + key: key ?? this.key, + value: value ?? this.value, + updatedAt: updatedAt ?? this.updatedAt, + ); + SettingsEntityData copyWithCompanion(i1.SettingsEntityCompanion data) { + return SettingsEntityData( + key: data.key.present ? data.key.value : this.key, + value: data.value.present ? data.value.value : this.value, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ); + } + + @override + String toString() { + return (StringBuffer('SettingsEntityData(') + ..write('key: $key, ') + ..write('value: $value, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(key, value, updatedAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i1.SettingsEntityData && + other.key == this.key && + other.value == this.value && + other.updatedAt == this.updatedAt); +} + +class SettingsEntityCompanion + extends i0.UpdateCompanion { + final i0.Value key; + final i0.Value value; + final i0.Value updatedAt; + const SettingsEntityCompanion({ + this.key = const i0.Value.absent(), + this.value = const i0.Value.absent(), + this.updatedAt = const i0.Value.absent(), + }); + SettingsEntityCompanion.insert({ + required String key, + required String value, + this.updatedAt = const i0.Value.absent(), + }) : key = i0.Value(key), + value = i0.Value(value); + static i0.Insertable custom({ + i0.Expression? key, + i0.Expression? value, + i0.Expression? updatedAt, + }) { + return i0.RawValuesInsertable({ + if (key != null) 'key': key, + if (value != null) 'value': value, + if (updatedAt != null) 'updated_at': updatedAt, + }); + } + + i1.SettingsEntityCompanion copyWith({ + i0.Value? key, + i0.Value? value, + i0.Value? updatedAt, + }) { + return i1.SettingsEntityCompanion( + key: key ?? this.key, + value: value ?? this.value, + updatedAt: updatedAt ?? this.updatedAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (key.present) { + map['key'] = i0.Variable(key.value); + } + if (value.present) { + map['value'] = i0.Variable(value.value); + } + if (updatedAt.present) { + map['updated_at'] = i0.Variable(updatedAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('SettingsEntityCompanion(') + ..write('key: $key, ') + ..write('value: $value, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } +} diff --git a/mobile/lib/infrastructure/loaders/image_request.dart b/mobile/lib/infrastructure/loaders/image_request.dart index 4df470277e..d0f3679084 100644 --- a/mobile/lib/infrastructure/loaders/image_request.dart +++ b/mobile/lib/infrastructure/loaders/image_request.dart @@ -2,15 +2,15 @@ import 'dart:async'; import 'dart:ffi'; import 'dart:ui' as ui; +import 'package:ffi/ffi.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:ffi/ffi.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/providers/infrastructure/platform.provider.dart'; part 'local_image_request.dart'; -part 'thumbhash_image_request.dart'; part 'remote_image_request.dart'; +part 'thumbhash_image_request.dart'; abstract class ImageRequest { static int _nextRequestId = 0; @@ -74,7 +74,9 @@ abstract class ImageRequest { Future _fromEncodedPlatformImage(int address, int length) async { final result = await _codecFromEncodedPlatformImage(address, length); - if (result == null) return null; + if (result == null) { + return null; + } final (codec, descriptor) = result; if (_isCancelled) { diff --git a/mobile/lib/infrastructure/loaders/local_image_request.dart b/mobile/lib/infrastructure/loaders/local_image_request.dart index a6c9fa2989..403e5d34cb 100644 --- a/mobile/lib/infrastructure/loaders/local_image_request.dart +++ b/mobile/lib/infrastructure/loaders/local_image_request.dart @@ -46,7 +46,9 @@ class LocalImageRequest extends ImageRequest { isVideo: assetType == AssetType.video, preferEncoded: true, ); - if (info == null) return null; + if (info == null) { + return null; + } final (codec, _) = await _codecFromEncodedPlatformImage(info['pointer']!, info['length']!) ?? (null, null); return codec; diff --git a/mobile/lib/infrastructure/loaders/remote_image_request.dart b/mobile/lib/infrastructure/loaders/remote_image_request.dart index bcfa9a93c7..da135f44d4 100644 --- a/mobile/lib/infrastructure/loaders/remote_image_request.dart +++ b/mobile/lib/infrastructure/loaders/remote_image_request.dart @@ -29,7 +29,9 @@ class RemoteImageRequest extends ImageRequest { } final info = await remoteImageApi.requestImage(uri, requestId: requestId, preferEncoded: true); - if (info == null) return null; + if (info == null) { + return null; + } final (codec, _) = await _codecFromEncodedPlatformImage(info['pointer']!, info['length']!) ?? (null, null); return codec; diff --git a/mobile/lib/infrastructure/repositories/api.repository.dart b/mobile/lib/infrastructure/repositories/api.repository.dart index 15696c65b7..9e11024539 100644 --- a/mobile/lib/infrastructure/repositories/api.repository.dart +++ b/mobile/lib/infrastructure/repositories/api.repository.dart @@ -5,7 +5,9 @@ class ApiRepository { Future checkNull(Future future) async { final response = await future; - if (response == null) throw const NoResponseDtoError(); + if (response == null) { + throw const NoResponseDtoError(); + } return response; } } diff --git a/mobile/lib/infrastructure/repositories/db.repository.dart b/mobile/lib/infrastructure/repositories/db.repository.dart index 0232f2600c..b6e12c341a 100644 --- a/mobile/lib/infrastructure/repositories/db.repository.dart +++ b/mobile/lib/infrastructure/repositories/db.repository.dart @@ -22,6 +22,7 @@ import 'package:immich_mobile/infrastructure/entities/remote_album_user.entity.d import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.dart'; import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/remote_asset_cloud_id.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/settings.entity.dart'; import 'package:immich_mobile/infrastructure/entities/stack.entity.dart'; import 'package:immich_mobile/infrastructure/entities/store.entity.dart'; import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity.dart'; @@ -30,6 +31,7 @@ import 'package:immich_mobile/infrastructure/entities/user.entity.dart'; import 'package:immich_mobile/infrastructure/entities/user_metadata.entity.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.drift.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.steps.dart'; +import 'package:logging/logging.dart'; @DriftDatabase( tables: [ @@ -54,6 +56,7 @@ import 'package:immich_mobile/infrastructure/repositories/db.repository.steps.da StoreEntity, TrashedLocalAssetEntity, AssetEditEntity, + SettingsEntity, AssetOcrEntity, ], include: {'package:immich_mobile/infrastructure/entities/merged_asset.drift'}, @@ -85,8 +88,19 @@ class Drift extends $Drift { }); } + Future optimize({bool allTables = false}) async { + try { + if (allTables) { + await customStatement('PRAGMA optimize=0x10002'); + } + await customStatement('PRAGMA optimize'); + } catch (error) { + Logger('Drift').fine('Failed to optimize database', error); + } + } + @override - int get schemaVersion => 25; + int get schemaVersion => 29; @override MigrationStrategy get migration => MigrationStrategy( @@ -253,7 +267,26 @@ class Drift extends $Drift { await m.alterTable(TableMigration(v24.remoteAlbumEntity)); }, from24To25: (m, v25) async { - await m.create(v25.assetOcrEntity); + await m.createTable(v25.metadata); + await customStatement('DROP INDEX IF EXISTS idx_remote_asset_owner_checksum'); + await customStatement('DROP INDEX IF EXISTS idx_remote_asset_local_date_time_day'); + await customStatement('DROP INDEX IF EXISTS idx_remote_asset_local_date_time_month'); + await m.createIndex(v25.idxRemoteAssetOwnerVisibilityDeletedCreated); + await m.createIndex(v25.idxRemoteExifCity); + await m.createIndex(v25.idxAssetFaceVisiblePerson); + }, + from25To26: (m, v26) async { + await m.addColumn(v26.remoteAssetEntity, v26.remoteAssetEntity.uploadedAt); + }, + from26To27: (m, v27) async { + await customStatement('ALTER TABLE metadata RENAME TO settings'); + }, + from27To28: (m, v28) async { + await m.createIndex(v28.idxLocalAssetCreatedAt); + }, + from28To29: (m, v29) async { + await m.createTable(v29.assetOcrEntity); + await m.createIndex(v29.idxAssetOcrAssetId); }, ), ); @@ -265,6 +298,7 @@ class Drift extends $Drift { } await customStatement('PRAGMA foreign_keys = ON;'); + await optimize(); }, beforeOpen: (details) async { await customStatement('PRAGMA foreign_keys = ON'); diff --git a/mobile/lib/infrastructure/repositories/db.repository.drift.dart b/mobile/lib/infrastructure/repositories/db.repository.drift.dart index dbfd53f484..887daea723 100644 --- a/mobile/lib/infrastructure/repositories/db.repository.drift.dart +++ b/mobile/lib/infrastructure/repositories/db.repository.drift.dart @@ -43,11 +43,13 @@ import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity as i20; import 'package:immich_mobile/infrastructure/entities/asset_edit.entity.drift.dart' as i21; -import 'package:immich_mobile/infrastructure/entities/asset_ocr.entity.drift.dart' +import 'package:immich_mobile/infrastructure/entities/settings.entity.drift.dart' as i22; -import 'package:immich_mobile/infrastructure/entities/merged_asset.drift.dart' +import 'package:immich_mobile/infrastructure/entities/asset_ocr.entity.drift.dart' as i23; -import 'package:drift/internal/modular.dart' as i24; +import 'package:immich_mobile/infrastructure/entities/merged_asset.drift.dart' + as i24; +import 'package:drift/internal/modular.dart' as i25; abstract class $Drift extends i0.GeneratedDatabase { $Drift(i0.QueryExecutor e) : super(e); @@ -91,12 +93,15 @@ abstract class $Drift extends i0.GeneratedDatabase { .$TrashedLocalAssetEntityTable(this); late final i21.$AssetEditEntityTable assetEditEntity = i21 .$AssetEditEntityTable(this); - late final i22.$AssetOcrEntityTable assetOcrEntity = i22.$AssetOcrEntityTable( + late final i22.$SettingsEntityTable settingsEntity = i22.$SettingsEntityTable( this, ); - i23.MergedAssetDrift get mergedAssetDrift => i24.ReadDatabaseContainer( + late final i23.$AssetOcrEntityTable assetOcrEntity = i23.$AssetOcrEntityTable( this, - ).accessor(i23.MergedAssetDrift.new); + ); + i24.MergedAssetDrift get mergedAssetDrift => i25.ReadDatabaseContainer( + this, + ).accessor(i24.MergedAssetDrift.new); @override Iterable> get allTables => allSchemaEntities.whereType>(); @@ -112,14 +117,13 @@ abstract class $Drift extends i0.GeneratedDatabase { i7.idxLocalAlbumAssetAlbumAsset, i4.idxLocalAssetChecksum, i4.idxLocalAssetCloudId, + i4.idxLocalAssetCreatedAt, i3.idxStackPrimaryAssetId, - i2.idxRemoteAssetOwnerChecksum, i2.uQRemoteAssetsOwnerChecksum, i2.uQRemoteAssetsOwnerLibraryChecksum, i2.idxRemoteAssetChecksum, i2.idxRemoteAssetStackId, - i2.idxRemoteAssetLocalDateTimeDay, - i2.idxRemoteAssetLocalDateTimeMonth, + i2.idxRemoteAssetOwnerVisibilityDeletedCreated, authUserEntity, userMetadataEntity, partnerEntity, @@ -134,17 +138,21 @@ abstract class $Drift extends i0.GeneratedDatabase { storeEntity, trashedLocalAssetEntity, assetEditEntity, + settingsEntity, assetOcrEntity, i10.idxPartnerSharedWithId, i11.idxLatLng, + i11.idxRemoteExifCity, i12.idxRemoteAlbumAssetAlbumAsset, i14.idxRemoteAssetCloudId, i17.idxPersonOwnerId, i18.idxAssetFacePersonId, i18.idxAssetFaceAssetId, + i18.idxAssetFaceVisiblePerson, i20.idxTrashedLocalAssetChecksum, i20.idxTrashedLocalAssetAlbum, i21.idxAssetEditAssetId, + i23.idxAssetOcrAssetId, ]; @override i0.StreamQueryUpdateRules @@ -402,6 +410,8 @@ class $DriftManager { ); i21.$$AssetEditEntityTableTableManager get assetEditEntity => i21.$$AssetEditEntityTableTableManager(_db, _db.assetEditEntity); - i22.$$AssetOcrEntityTableTableManager get assetOcrEntity => - i22.$$AssetOcrEntityTableTableManager(_db, _db.assetOcrEntity); + i22.$$SettingsEntityTableTableManager get settingsEntity => + i22.$$SettingsEntityTableTableManager(_db, _db.settingsEntity); + i23.$$AssetOcrEntityTableTableManager get assetOcrEntity => + i23.$$AssetOcrEntityTableTableManager(_db, _db.assetOcrEntity); } diff --git a/mobile/lib/infrastructure/repositories/db.repository.steps.dart b/mobile/lib/infrastructure/repositories/db.repository.steps.dart index 19cc7d921a..72d72d69e9 100644 --- a/mobile/lib/infrastructure/repositories/db.repository.steps.dart +++ b/mobile/lib/infrastructure/repositories/db.repository.steps.dart @@ -12390,13 +12390,11 @@ final class Schema25 extends i0.VersionedSchema { idxLocalAssetChecksum, idxLocalAssetCloudId, idxStackPrimaryAssetId, - idxRemoteAssetOwnerChecksum, uQRemoteAssetsOwnerChecksum, uQRemoteAssetsOwnerLibraryChecksum, idxRemoteAssetChecksum, idxRemoteAssetStackId, - idxRemoteAssetLocalDateTimeDay, - idxRemoteAssetLocalDateTimeMonth, + idxRemoteAssetOwnerVisibilityDeletedCreated, authUserEntity, userMetadataEntity, partnerEntity, @@ -12411,14 +12409,16 @@ final class Schema25 extends i0.VersionedSchema { storeEntity, trashedLocalAssetEntity, assetEditEntity, - assetOcrEntity, + metadata, idxPartnerSharedWithId, idxLatLng, + idxRemoteExifCity, idxRemoteAlbumAssetAlbumAsset, idxRemoteAssetCloudId, idxPersonOwnerId, idxAssetFacePersonId, idxAssetFaceAssetId, + idxAssetFaceVisiblePerson, idxTrashedLocalAssetChecksum, idxTrashedLocalAssetAlbum, idxAssetEditAssetId, @@ -12583,10 +12583,6 @@ final class Schema25 extends i0.VersionedSchema { 'idx_stack_primary_asset_id', 'CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)', ); - final i1.Index idxRemoteAssetOwnerChecksum = i1.Index( - 'idx_remote_asset_owner_checksum', - 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)', - ); final i1.Index uQRemoteAssetsOwnerChecksum = i1.Index( 'UQ_remote_assets_owner_checksum', 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)', @@ -12603,13 +12599,9 @@ final class Schema25 extends i0.VersionedSchema { 'idx_remote_asset_stack_id', 'CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)', ); - final i1.Index idxRemoteAssetLocalDateTimeDay = i1.Index( - 'idx_remote_asset_local_date_time_day', - 'CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_day ON remote_asset_entity (STRFTIME(\'%Y-%m-%d\', local_date_time))', - ); - final i1.Index idxRemoteAssetLocalDateTimeMonth = i1.Index( - 'idx_remote_asset_local_date_time_month', - 'CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_month ON remote_asset_entity (STRFTIME(\'%Y-%m\', local_date_time))', + final i1.Index idxRemoteAssetOwnerVisibilityDeletedCreated = i1.Index( + 'idx_remote_asset_owner_visibility_deleted_created', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_visibility_deleted_created ON remote_asset_entity (owner_id, visibility, deleted_at, created_at DESC)', ); late final Shape40 authUserEntity = Shape40( source: i0.VersionedTable( @@ -12864,28 +12856,13 @@ final class Schema25 extends i0.VersionedSchema { ), alias: null, ); - late final Shape49 assetOcrEntity = Shape49( + late final Shape49 metadata = Shape49( source: i0.VersionedTable( - entityName: 'asset_ocr_entity', + entityName: 'metadata', withoutRowId: true, isStrict: true, - tableConstraints: ['PRIMARY KEY(id)'], - columns: [ - _column_107, - _column_159, - _column_210, - _column_211, - _column_212, - _column_213, - _column_214, - _column_215, - _column_216, - _column_217, - _column_218, - _column_219, - _column_220, - _column_201, - ], + tableConstraints: ['PRIMARY KEY("key")'], + columns: [_column_210, _column_211, _column_115], attachedDatabase: database, ), alias: null, @@ -12898,6 +12875,10 @@ final class Schema25 extends i0.VersionedSchema { 'idx_lat_lng', 'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)', ); + final i1.Index idxRemoteExifCity = i1.Index( + 'idx_remote_exif_city', + 'CREATE INDEX IF NOT EXISTS idx_remote_exif_city ON remote_exif_entity (city) WHERE city IS NOT NULL', + ); final i1.Index idxRemoteAlbumAssetAlbumAsset = i1.Index( 'idx_remote_album_asset_album_asset', 'CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)', @@ -12918,6 +12899,10 @@ final class Schema25 extends i0.VersionedSchema { 'idx_asset_face_asset_id', 'CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)', ); + final i1.Index idxAssetFaceVisiblePerson = i1.Index( + 'idx_asset_face_visible_person', + 'CREATE INDEX IF NOT EXISTS idx_asset_face_visible_person ON asset_face_entity (person_id, asset_id) WHERE is_visible = 1 AND deleted_at IS NULL', + ); final i1.Index idxTrashedLocalAssetChecksum = i1.Index( 'idx_trashed_local_asset_checksum', 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)', @@ -12934,6 +12919,2300 @@ final class Schema25 extends i0.VersionedSchema { class Shape49 extends i0.VersionedTable { Shape49({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get key => + columnsByName['key']! as i1.GeneratedColumn; + i1.GeneratedColumn get value => + columnsByName['value']! as i1.GeneratedColumn; + i1.GeneratedColumn get updatedAt => + columnsByName['updated_at']! as i1.GeneratedColumn; +} + +i1.GeneratedColumn _column_210(String aliasedName) => + i1.GeneratedColumn( + 'key', + aliasedName, + false, + type: i1.DriftSqlType.string, + $customConstraints: 'NOT NULL', + ); +i1.GeneratedColumn _column_211(String aliasedName) => + i1.GeneratedColumn( + 'value', + aliasedName, + false, + type: i1.DriftSqlType.string, + $customConstraints: 'NOT NULL', + ); + +final class Schema26 extends i0.VersionedSchema { + Schema26({required super.database}) : super(version: 26); + @override + late final List entities = [ + userEntity, + remoteAssetEntity, + stackEntity, + localAssetEntity, + remoteAlbumEntity, + localAlbumEntity, + localAlbumAssetEntity, + idxLocalAlbumAssetAlbumAsset, + idxLocalAssetChecksum, + idxLocalAssetCloudId, + idxStackPrimaryAssetId, + uQRemoteAssetsOwnerChecksum, + uQRemoteAssetsOwnerLibraryChecksum, + idxRemoteAssetChecksum, + idxRemoteAssetStackId, + idxRemoteAssetOwnerVisibilityDeletedCreated, + authUserEntity, + userMetadataEntity, + partnerEntity, + remoteExifEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + remoteAssetCloudIdEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + assetFaceEntity, + storeEntity, + trashedLocalAssetEntity, + assetEditEntity, + metadata, + idxPartnerSharedWithId, + idxLatLng, + idxRemoteExifCity, + idxRemoteAlbumAssetAlbumAsset, + idxRemoteAssetCloudId, + idxPersonOwnerId, + idxAssetFacePersonId, + idxAssetFaceAssetId, + idxAssetFaceVisiblePerson, + idxTrashedLocalAssetChecksum, + idxTrashedLocalAssetAlbum, + idxAssetEditAssetId, + ]; + late final Shape33 userEntity = Shape33( + source: i0.VersionedTable( + entityName: 'user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_108, + _column_109, + _column_110, + _column_111, + _column_112, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape50 remoteAssetEntity = Shape50( + source: i0.VersionedTable( + entityName: 'remote_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_108, + _column_113, + _column_114, + _column_115, + _column_116, + _column_117, + _column_118, + _column_107, + _column_119, + _column_120, + _column_121, + _column_122, + _column_123, + _column_124, + _column_212, + _column_125, + _column_126, + _column_127, + _column_128, + _column_129, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape35 stackEntity = Shape35( + source: i0.VersionedTable( + entityName: 'stack_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_114, + _column_115, + _column_121, + _column_130, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape36 localAssetEntity = Shape36( + source: i0.VersionedTable( + entityName: 'local_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_108, + _column_113, + _column_114, + _column_115, + _column_116, + _column_117, + _column_118, + _column_107, + _column_131, + _column_120, + _column_132, + _column_133, + _column_134, + _column_135, + _column_136, + _column_137, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape48 remoteAlbumEntity = Shape48( + source: i0.VersionedTable( + entityName: 'remote_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_108, + _column_138, + _column_114, + _column_115, + _column_139, + _column_140, + _column_141, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape38 localAlbumEntity = Shape38( + source: i0.VersionedTable( + entityName: 'local_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_108, + _column_115, + _column_142, + _column_143, + _column_144, + _column_145, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape39 localAlbumAssetEntity = Shape39( + source: i0.VersionedTable( + entityName: 'local_album_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, album_id)'], + columns: [_column_146, _column_147, _column_145], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Index idxLocalAlbumAssetAlbumAsset = i1.Index( + 'idx_local_album_asset_album_asset', + 'CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)', + ); + final i1.Index idxLocalAssetChecksum = i1.Index( + 'idx_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)', + ); + final i1.Index idxLocalAssetCloudId = i1.Index( + 'idx_local_asset_cloud_id', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)', + ); + final i1.Index idxStackPrimaryAssetId = i1.Index( + 'idx_stack_primary_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)', + ); + final i1.Index uQRemoteAssetsOwnerChecksum = i1.Index( + 'UQ_remote_assets_owner_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)', + ); + final i1.Index uQRemoteAssetsOwnerLibraryChecksum = i1.Index( + 'UQ_remote_assets_owner_library_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)', + ); + final i1.Index idxRemoteAssetChecksum = i1.Index( + 'idx_remote_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)', + ); + final i1.Index idxRemoteAssetStackId = i1.Index( + 'idx_remote_asset_stack_id', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)', + ); + final i1.Index idxRemoteAssetOwnerVisibilityDeletedCreated = i1.Index( + 'idx_remote_asset_owner_visibility_deleted_created', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_visibility_deleted_created ON remote_asset_entity (owner_id, visibility, deleted_at, created_at DESC)', + ); + late final Shape40 authUserEntity = Shape40( + source: i0.VersionedTable( + entityName: 'auth_user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_108, + _column_109, + _column_148, + _column_110, + _column_111, + _column_149, + _column_150, + _column_151, + _column_152, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape4 userMetadataEntity = Shape4( + source: i0.VersionedTable( + entityName: 'user_metadata_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(user_id, "key")'], + columns: [_column_153, _column_154, _column_155], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape41 partnerEntity = Shape41( + source: i0.VersionedTable( + entityName: 'partner_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(shared_by_id, shared_with_id)'], + columns: [_column_156, _column_157, _column_158], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape42 remoteExifEntity = Shape42( + source: i0.VersionedTable( + entityName: 'remote_exif_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id)'], + columns: [ + _column_159, + _column_160, + _column_161, + _column_162, + _column_163, + _column_164, + _column_117, + _column_116, + _column_165, + _column_166, + _column_167, + _column_168, + _column_135, + _column_136, + _column_169, + _column_170, + _column_171, + _column_172, + _column_173, + _column_174, + _column_175, + _column_176, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape7 remoteAlbumAssetEntity = Shape7( + source: i0.VersionedTable( + entityName: 'remote_album_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, album_id)'], + columns: [_column_159, _column_177], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape10 remoteAlbumUserEntity = Shape10( + source: i0.VersionedTable( + entityName: 'remote_album_user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(album_id, user_id)'], + columns: [_column_177, _column_153, _column_178], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape43 remoteAssetCloudIdEntity = Shape43( + source: i0.VersionedTable( + entityName: 'remote_asset_cloud_id_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id)'], + columns: [ + _column_159, + _column_179, + _column_180, + _column_134, + _column_135, + _column_136, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape44 memoryEntity = Shape44( + source: i0.VersionedTable( + entityName: 'memory_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_114, + _column_115, + _column_124, + _column_121, + _column_113, + _column_181, + _column_182, + _column_183, + _column_184, + _column_185, + _column_186, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape12 memoryAssetEntity = Shape12( + source: i0.VersionedTable( + entityName: 'memory_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, memory_id)'], + columns: [_column_159, _column_187], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape45 personEntity = Shape45( + source: i0.VersionedTable( + entityName: 'person_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_114, + _column_115, + _column_121, + _column_108, + _column_188, + _column_189, + _column_190, + _column_191, + _column_192, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape46 assetFaceEntity = Shape46( + source: i0.VersionedTable( + entityName: 'asset_face_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_159, + _column_193, + _column_194, + _column_195, + _column_196, + _column_197, + _column_198, + _column_199, + _column_200, + _column_201, + _column_124, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape18 storeEntity = Shape18( + source: i0.VersionedTable( + entityName: 'store_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [_column_202, _column_203, _column_204], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape47 trashedLocalAssetEntity = Shape47( + source: i0.VersionedTable( + entityName: 'trashed_local_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id, album_id)'], + columns: [ + _column_108, + _column_113, + _column_114, + _column_115, + _column_116, + _column_117, + _column_118, + _column_107, + _column_205, + _column_131, + _column_120, + _column_132, + _column_206, + _column_137, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape32 assetEditEntity = Shape32( + source: i0.VersionedTable( + entityName: 'asset_edit_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_159, + _column_207, + _column_208, + _column_209, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape49 metadata = Shape49( + source: i0.VersionedTable( + entityName: 'metadata', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY("key")'], + columns: [_column_210, _column_211, _column_115], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Index idxPartnerSharedWithId = i1.Index( + 'idx_partner_shared_with_id', + 'CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)', + ); + final i1.Index idxLatLng = i1.Index( + 'idx_lat_lng', + 'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)', + ); + final i1.Index idxRemoteExifCity = i1.Index( + 'idx_remote_exif_city', + 'CREATE INDEX IF NOT EXISTS idx_remote_exif_city ON remote_exif_entity (city) WHERE city IS NOT NULL', + ); + final i1.Index idxRemoteAlbumAssetAlbumAsset = i1.Index( + 'idx_remote_album_asset_album_asset', + 'CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)', + ); + final i1.Index idxRemoteAssetCloudId = i1.Index( + 'idx_remote_asset_cloud_id', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)', + ); + final i1.Index idxPersonOwnerId = i1.Index( + 'idx_person_owner_id', + 'CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)', + ); + final i1.Index idxAssetFacePersonId = i1.Index( + 'idx_asset_face_person_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)', + ); + final i1.Index idxAssetFaceAssetId = i1.Index( + 'idx_asset_face_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)', + ); + final i1.Index idxAssetFaceVisiblePerson = i1.Index( + 'idx_asset_face_visible_person', + 'CREATE INDEX IF NOT EXISTS idx_asset_face_visible_person ON asset_face_entity (person_id, asset_id) WHERE is_visible = 1 AND deleted_at IS NULL', + ); + final i1.Index idxTrashedLocalAssetChecksum = i1.Index( + 'idx_trashed_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)', + ); + final i1.Index idxTrashedLocalAssetAlbum = i1.Index( + 'idx_trashed_local_asset_album', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)', + ); + final i1.Index idxAssetEditAssetId = i1.Index( + 'idx_asset_edit_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_edit_asset_id ON asset_edit_entity (asset_id)', + ); +} + +class Shape50 extends i0.VersionedTable { + Shape50({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get name => + columnsByName['name']! as i1.GeneratedColumn; + i1.GeneratedColumn get type => + columnsByName['type']! as i1.GeneratedColumn; + i1.GeneratedColumn get createdAt => + columnsByName['created_at']! as i1.GeneratedColumn; + i1.GeneratedColumn get updatedAt => + columnsByName['updated_at']! as i1.GeneratedColumn; + i1.GeneratedColumn get width => + columnsByName['width']! as i1.GeneratedColumn; + i1.GeneratedColumn get height => + columnsByName['height']! as i1.GeneratedColumn; + i1.GeneratedColumn get durationMs => + columnsByName['duration_ms']! as i1.GeneratedColumn; + i1.GeneratedColumn get id => + columnsByName['id']! as i1.GeneratedColumn; + i1.GeneratedColumn get checksum => + columnsByName['checksum']! as i1.GeneratedColumn; + i1.GeneratedColumn get isFavorite => + columnsByName['is_favorite']! as i1.GeneratedColumn; + i1.GeneratedColumn get ownerId => + columnsByName['owner_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get localDateTime => + columnsByName['local_date_time']! as i1.GeneratedColumn; + i1.GeneratedColumn get thumbHash => + columnsByName['thumb_hash']! as i1.GeneratedColumn; + i1.GeneratedColumn get deletedAt => + columnsByName['deleted_at']! as i1.GeneratedColumn; + i1.GeneratedColumn get uploadedAt => + columnsByName['uploaded_at']! as i1.GeneratedColumn; + i1.GeneratedColumn get livePhotoVideoId => + columnsByName['live_photo_video_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get visibility => + columnsByName['visibility']! as i1.GeneratedColumn; + i1.GeneratedColumn get stackId => + columnsByName['stack_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get libraryId => + columnsByName['library_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get isEdited => + columnsByName['is_edited']! as i1.GeneratedColumn; +} + +i1.GeneratedColumn _column_212(String aliasedName) => + i1.GeneratedColumn( + 'uploaded_at', + aliasedName, + true, + type: i1.DriftSqlType.string, + $customConstraints: 'NULL', + ); + +final class Schema27 extends i0.VersionedSchema { + Schema27({required super.database}) : super(version: 27); + @override + late final List entities = [ + userEntity, + remoteAssetEntity, + stackEntity, + localAssetEntity, + remoteAlbumEntity, + localAlbumEntity, + localAlbumAssetEntity, + idxLocalAlbumAssetAlbumAsset, + idxLocalAssetChecksum, + idxLocalAssetCloudId, + idxStackPrimaryAssetId, + uQRemoteAssetsOwnerChecksum, + uQRemoteAssetsOwnerLibraryChecksum, + idxRemoteAssetChecksum, + idxRemoteAssetStackId, + idxRemoteAssetOwnerVisibilityDeletedCreated, + authUserEntity, + userMetadataEntity, + partnerEntity, + remoteExifEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + remoteAssetCloudIdEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + assetFaceEntity, + storeEntity, + trashedLocalAssetEntity, + assetEditEntity, + settings, + idxPartnerSharedWithId, + idxLatLng, + idxRemoteExifCity, + idxRemoteAlbumAssetAlbumAsset, + idxRemoteAssetCloudId, + idxPersonOwnerId, + idxAssetFacePersonId, + idxAssetFaceAssetId, + idxAssetFaceVisiblePerson, + idxTrashedLocalAssetChecksum, + idxTrashedLocalAssetAlbum, + idxAssetEditAssetId, + ]; + late final Shape33 userEntity = Shape33( + source: i0.VersionedTable( + entityName: 'user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_108, + _column_109, + _column_110, + _column_111, + _column_112, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape50 remoteAssetEntity = Shape50( + source: i0.VersionedTable( + entityName: 'remote_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_108, + _column_113, + _column_114, + _column_115, + _column_116, + _column_117, + _column_118, + _column_107, + _column_119, + _column_120, + _column_121, + _column_122, + _column_123, + _column_124, + _column_212, + _column_125, + _column_126, + _column_127, + _column_128, + _column_129, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape35 stackEntity = Shape35( + source: i0.VersionedTable( + entityName: 'stack_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_114, + _column_115, + _column_121, + _column_130, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape36 localAssetEntity = Shape36( + source: i0.VersionedTable( + entityName: 'local_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_108, + _column_113, + _column_114, + _column_115, + _column_116, + _column_117, + _column_118, + _column_107, + _column_131, + _column_120, + _column_132, + _column_133, + _column_134, + _column_135, + _column_136, + _column_137, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape48 remoteAlbumEntity = Shape48( + source: i0.VersionedTable( + entityName: 'remote_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_108, + _column_138, + _column_114, + _column_115, + _column_139, + _column_140, + _column_141, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape38 localAlbumEntity = Shape38( + source: i0.VersionedTable( + entityName: 'local_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_108, + _column_115, + _column_142, + _column_143, + _column_144, + _column_145, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape39 localAlbumAssetEntity = Shape39( + source: i0.VersionedTable( + entityName: 'local_album_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, album_id)'], + columns: [_column_146, _column_147, _column_145], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Index idxLocalAlbumAssetAlbumAsset = i1.Index( + 'idx_local_album_asset_album_asset', + 'CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)', + ); + final i1.Index idxLocalAssetChecksum = i1.Index( + 'idx_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)', + ); + final i1.Index idxLocalAssetCloudId = i1.Index( + 'idx_local_asset_cloud_id', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)', + ); + final i1.Index idxStackPrimaryAssetId = i1.Index( + 'idx_stack_primary_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)', + ); + final i1.Index uQRemoteAssetsOwnerChecksum = i1.Index( + 'UQ_remote_assets_owner_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)', + ); + final i1.Index uQRemoteAssetsOwnerLibraryChecksum = i1.Index( + 'UQ_remote_assets_owner_library_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)', + ); + final i1.Index idxRemoteAssetChecksum = i1.Index( + 'idx_remote_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)', + ); + final i1.Index idxRemoteAssetStackId = i1.Index( + 'idx_remote_asset_stack_id', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)', + ); + final i1.Index idxRemoteAssetOwnerVisibilityDeletedCreated = i1.Index( + 'idx_remote_asset_owner_visibility_deleted_created', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_visibility_deleted_created ON remote_asset_entity (owner_id, visibility, deleted_at, created_at DESC)', + ); + late final Shape40 authUserEntity = Shape40( + source: i0.VersionedTable( + entityName: 'auth_user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_108, + _column_109, + _column_148, + _column_110, + _column_111, + _column_149, + _column_150, + _column_151, + _column_152, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape4 userMetadataEntity = Shape4( + source: i0.VersionedTable( + entityName: 'user_metadata_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(user_id, "key")'], + columns: [_column_153, _column_154, _column_155], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape41 partnerEntity = Shape41( + source: i0.VersionedTable( + entityName: 'partner_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(shared_by_id, shared_with_id)'], + columns: [_column_156, _column_157, _column_158], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape42 remoteExifEntity = Shape42( + source: i0.VersionedTable( + entityName: 'remote_exif_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id)'], + columns: [ + _column_159, + _column_160, + _column_161, + _column_162, + _column_163, + _column_164, + _column_117, + _column_116, + _column_165, + _column_166, + _column_167, + _column_168, + _column_135, + _column_136, + _column_169, + _column_170, + _column_171, + _column_172, + _column_173, + _column_174, + _column_175, + _column_176, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape7 remoteAlbumAssetEntity = Shape7( + source: i0.VersionedTable( + entityName: 'remote_album_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, album_id)'], + columns: [_column_159, _column_177], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape10 remoteAlbumUserEntity = Shape10( + source: i0.VersionedTable( + entityName: 'remote_album_user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(album_id, user_id)'], + columns: [_column_177, _column_153, _column_178], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape43 remoteAssetCloudIdEntity = Shape43( + source: i0.VersionedTable( + entityName: 'remote_asset_cloud_id_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id)'], + columns: [ + _column_159, + _column_179, + _column_180, + _column_134, + _column_135, + _column_136, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape44 memoryEntity = Shape44( + source: i0.VersionedTable( + entityName: 'memory_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_114, + _column_115, + _column_124, + _column_121, + _column_113, + _column_181, + _column_182, + _column_183, + _column_184, + _column_185, + _column_186, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape12 memoryAssetEntity = Shape12( + source: i0.VersionedTable( + entityName: 'memory_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, memory_id)'], + columns: [_column_159, _column_187], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape45 personEntity = Shape45( + source: i0.VersionedTable( + entityName: 'person_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_114, + _column_115, + _column_121, + _column_108, + _column_188, + _column_189, + _column_190, + _column_191, + _column_192, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape46 assetFaceEntity = Shape46( + source: i0.VersionedTable( + entityName: 'asset_face_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_159, + _column_193, + _column_194, + _column_195, + _column_196, + _column_197, + _column_198, + _column_199, + _column_200, + _column_201, + _column_124, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape18 storeEntity = Shape18( + source: i0.VersionedTable( + entityName: 'store_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [_column_202, _column_203, _column_204], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape47 trashedLocalAssetEntity = Shape47( + source: i0.VersionedTable( + entityName: 'trashed_local_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id, album_id)'], + columns: [ + _column_108, + _column_113, + _column_114, + _column_115, + _column_116, + _column_117, + _column_118, + _column_107, + _column_205, + _column_131, + _column_120, + _column_132, + _column_206, + _column_137, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape32 assetEditEntity = Shape32( + source: i0.VersionedTable( + entityName: 'asset_edit_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_159, + _column_207, + _column_208, + _column_209, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape49 settings = Shape49( + source: i0.VersionedTable( + entityName: 'settings', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY("key")'], + columns: [_column_210, _column_211, _column_115], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Index idxPartnerSharedWithId = i1.Index( + 'idx_partner_shared_with_id', + 'CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)', + ); + final i1.Index idxLatLng = i1.Index( + 'idx_lat_lng', + 'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)', + ); + final i1.Index idxRemoteExifCity = i1.Index( + 'idx_remote_exif_city', + 'CREATE INDEX IF NOT EXISTS idx_remote_exif_city ON remote_exif_entity (city) WHERE city IS NOT NULL', + ); + final i1.Index idxRemoteAlbumAssetAlbumAsset = i1.Index( + 'idx_remote_album_asset_album_asset', + 'CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)', + ); + final i1.Index idxRemoteAssetCloudId = i1.Index( + 'idx_remote_asset_cloud_id', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)', + ); + final i1.Index idxPersonOwnerId = i1.Index( + 'idx_person_owner_id', + 'CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)', + ); + final i1.Index idxAssetFacePersonId = i1.Index( + 'idx_asset_face_person_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)', + ); + final i1.Index idxAssetFaceAssetId = i1.Index( + 'idx_asset_face_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)', + ); + final i1.Index idxAssetFaceVisiblePerson = i1.Index( + 'idx_asset_face_visible_person', + 'CREATE INDEX IF NOT EXISTS idx_asset_face_visible_person ON asset_face_entity (person_id, asset_id) WHERE is_visible = 1 AND deleted_at IS NULL', + ); + final i1.Index idxTrashedLocalAssetChecksum = i1.Index( + 'idx_trashed_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)', + ); + final i1.Index idxTrashedLocalAssetAlbum = i1.Index( + 'idx_trashed_local_asset_album', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)', + ); + final i1.Index idxAssetEditAssetId = i1.Index( + 'idx_asset_edit_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_edit_asset_id ON asset_edit_entity (asset_id)', + ); +} + +final class Schema28 extends i0.VersionedSchema { + Schema28({required super.database}) : super(version: 28); + @override + late final List entities = [ + userEntity, + remoteAssetEntity, + stackEntity, + localAssetEntity, + remoteAlbumEntity, + localAlbumEntity, + localAlbumAssetEntity, + idxLocalAlbumAssetAlbumAsset, + idxLocalAssetChecksum, + idxLocalAssetCloudId, + idxLocalAssetCreatedAt, + idxStackPrimaryAssetId, + uQRemoteAssetsOwnerChecksum, + uQRemoteAssetsOwnerLibraryChecksum, + idxRemoteAssetChecksum, + idxRemoteAssetStackId, + idxRemoteAssetOwnerVisibilityDeletedCreated, + authUserEntity, + userMetadataEntity, + partnerEntity, + remoteExifEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + remoteAssetCloudIdEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + assetFaceEntity, + storeEntity, + trashedLocalAssetEntity, + assetEditEntity, + settings, + idxPartnerSharedWithId, + idxLatLng, + idxRemoteExifCity, + idxRemoteAlbumAssetAlbumAsset, + idxRemoteAssetCloudId, + idxPersonOwnerId, + idxAssetFacePersonId, + idxAssetFaceAssetId, + idxAssetFaceVisiblePerson, + idxTrashedLocalAssetChecksum, + idxTrashedLocalAssetAlbum, + idxAssetEditAssetId, + ]; + late final Shape33 userEntity = Shape33( + source: i0.VersionedTable( + entityName: 'user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_108, + _column_109, + _column_110, + _column_111, + _column_112, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape50 remoteAssetEntity = Shape50( + source: i0.VersionedTable( + entityName: 'remote_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_108, + _column_113, + _column_114, + _column_115, + _column_116, + _column_117, + _column_118, + _column_107, + _column_119, + _column_120, + _column_121, + _column_122, + _column_123, + _column_124, + _column_212, + _column_125, + _column_126, + _column_127, + _column_128, + _column_129, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape35 stackEntity = Shape35( + source: i0.VersionedTable( + entityName: 'stack_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_114, + _column_115, + _column_121, + _column_130, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape36 localAssetEntity = Shape36( + source: i0.VersionedTable( + entityName: 'local_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_108, + _column_113, + _column_114, + _column_115, + _column_116, + _column_117, + _column_118, + _column_107, + _column_131, + _column_120, + _column_132, + _column_133, + _column_134, + _column_135, + _column_136, + _column_137, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape48 remoteAlbumEntity = Shape48( + source: i0.VersionedTable( + entityName: 'remote_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_108, + _column_138, + _column_114, + _column_115, + _column_139, + _column_140, + _column_141, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape38 localAlbumEntity = Shape38( + source: i0.VersionedTable( + entityName: 'local_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_108, + _column_115, + _column_142, + _column_143, + _column_144, + _column_145, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape39 localAlbumAssetEntity = Shape39( + source: i0.VersionedTable( + entityName: 'local_album_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, album_id)'], + columns: [_column_146, _column_147, _column_145], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Index idxLocalAlbumAssetAlbumAsset = i1.Index( + 'idx_local_album_asset_album_asset', + 'CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)', + ); + final i1.Index idxLocalAssetChecksum = i1.Index( + 'idx_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)', + ); + final i1.Index idxLocalAssetCloudId = i1.Index( + 'idx_local_asset_cloud_id', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)', + ); + final i1.Index idxLocalAssetCreatedAt = i1.Index( + 'idx_local_asset_created_at', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_created_at ON local_asset_entity (created_at)', + ); + final i1.Index idxStackPrimaryAssetId = i1.Index( + 'idx_stack_primary_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)', + ); + final i1.Index uQRemoteAssetsOwnerChecksum = i1.Index( + 'UQ_remote_assets_owner_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)', + ); + final i1.Index uQRemoteAssetsOwnerLibraryChecksum = i1.Index( + 'UQ_remote_assets_owner_library_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)', + ); + final i1.Index idxRemoteAssetChecksum = i1.Index( + 'idx_remote_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)', + ); + final i1.Index idxRemoteAssetStackId = i1.Index( + 'idx_remote_asset_stack_id', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)', + ); + final i1.Index idxRemoteAssetOwnerVisibilityDeletedCreated = i1.Index( + 'idx_remote_asset_owner_visibility_deleted_created', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_visibility_deleted_created ON remote_asset_entity (owner_id, visibility, deleted_at, created_at DESC)', + ); + late final Shape40 authUserEntity = Shape40( + source: i0.VersionedTable( + entityName: 'auth_user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_108, + _column_109, + _column_148, + _column_110, + _column_111, + _column_149, + _column_150, + _column_151, + _column_152, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape4 userMetadataEntity = Shape4( + source: i0.VersionedTable( + entityName: 'user_metadata_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(user_id, "key")'], + columns: [_column_153, _column_154, _column_155], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape41 partnerEntity = Shape41( + source: i0.VersionedTable( + entityName: 'partner_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(shared_by_id, shared_with_id)'], + columns: [_column_156, _column_157, _column_158], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape42 remoteExifEntity = Shape42( + source: i0.VersionedTable( + entityName: 'remote_exif_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id)'], + columns: [ + _column_159, + _column_160, + _column_161, + _column_162, + _column_163, + _column_164, + _column_117, + _column_116, + _column_165, + _column_166, + _column_167, + _column_168, + _column_135, + _column_136, + _column_169, + _column_170, + _column_171, + _column_172, + _column_173, + _column_174, + _column_175, + _column_176, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape7 remoteAlbumAssetEntity = Shape7( + source: i0.VersionedTable( + entityName: 'remote_album_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, album_id)'], + columns: [_column_159, _column_177], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape10 remoteAlbumUserEntity = Shape10( + source: i0.VersionedTable( + entityName: 'remote_album_user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(album_id, user_id)'], + columns: [_column_177, _column_153, _column_178], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape43 remoteAssetCloudIdEntity = Shape43( + source: i0.VersionedTable( + entityName: 'remote_asset_cloud_id_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id)'], + columns: [ + _column_159, + _column_179, + _column_180, + _column_134, + _column_135, + _column_136, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape44 memoryEntity = Shape44( + source: i0.VersionedTable( + entityName: 'memory_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_114, + _column_115, + _column_124, + _column_121, + _column_113, + _column_181, + _column_182, + _column_183, + _column_184, + _column_185, + _column_186, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape12 memoryAssetEntity = Shape12( + source: i0.VersionedTable( + entityName: 'memory_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, memory_id)'], + columns: [_column_159, _column_187], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape45 personEntity = Shape45( + source: i0.VersionedTable( + entityName: 'person_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_114, + _column_115, + _column_121, + _column_108, + _column_188, + _column_189, + _column_190, + _column_191, + _column_192, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape46 assetFaceEntity = Shape46( + source: i0.VersionedTable( + entityName: 'asset_face_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_159, + _column_193, + _column_194, + _column_195, + _column_196, + _column_197, + _column_198, + _column_199, + _column_200, + _column_201, + _column_124, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape18 storeEntity = Shape18( + source: i0.VersionedTable( + entityName: 'store_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [_column_202, _column_203, _column_204], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape47 trashedLocalAssetEntity = Shape47( + source: i0.VersionedTable( + entityName: 'trashed_local_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id, album_id)'], + columns: [ + _column_108, + _column_113, + _column_114, + _column_115, + _column_116, + _column_117, + _column_118, + _column_107, + _column_205, + _column_131, + _column_120, + _column_132, + _column_206, + _column_137, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape32 assetEditEntity = Shape32( + source: i0.VersionedTable( + entityName: 'asset_edit_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_159, + _column_207, + _column_208, + _column_209, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape49 settings = Shape49( + source: i0.VersionedTable( + entityName: 'settings', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY("key")'], + columns: [_column_210, _column_211, _column_115], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Index idxPartnerSharedWithId = i1.Index( + 'idx_partner_shared_with_id', + 'CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)', + ); + final i1.Index idxLatLng = i1.Index( + 'idx_lat_lng', + 'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)', + ); + final i1.Index idxRemoteExifCity = i1.Index( + 'idx_remote_exif_city', + 'CREATE INDEX IF NOT EXISTS idx_remote_exif_city ON remote_exif_entity (city) WHERE city IS NOT NULL', + ); + final i1.Index idxRemoteAlbumAssetAlbumAsset = i1.Index( + 'idx_remote_album_asset_album_asset', + 'CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)', + ); + final i1.Index idxRemoteAssetCloudId = i1.Index( + 'idx_remote_asset_cloud_id', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)', + ); + final i1.Index idxPersonOwnerId = i1.Index( + 'idx_person_owner_id', + 'CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)', + ); + final i1.Index idxAssetFacePersonId = i1.Index( + 'idx_asset_face_person_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)', + ); + final i1.Index idxAssetFaceAssetId = i1.Index( + 'idx_asset_face_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)', + ); + final i1.Index idxAssetFaceVisiblePerson = i1.Index( + 'idx_asset_face_visible_person', + 'CREATE INDEX IF NOT EXISTS idx_asset_face_visible_person ON asset_face_entity (person_id, asset_id) WHERE is_visible = 1 AND deleted_at IS NULL', + ); + final i1.Index idxTrashedLocalAssetChecksum = i1.Index( + 'idx_trashed_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)', + ); + final i1.Index idxTrashedLocalAssetAlbum = i1.Index( + 'idx_trashed_local_asset_album', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)', + ); + final i1.Index idxAssetEditAssetId = i1.Index( + 'idx_asset_edit_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_edit_asset_id ON asset_edit_entity (asset_id)', + ); +} + +final class Schema29 extends i0.VersionedSchema { + Schema29({required super.database}) : super(version: 29); + @override + late final List entities = [ + userEntity, + remoteAssetEntity, + stackEntity, + localAssetEntity, + remoteAlbumEntity, + localAlbumEntity, + localAlbumAssetEntity, + idxLocalAlbumAssetAlbumAsset, + idxLocalAssetChecksum, + idxLocalAssetCloudId, + idxLocalAssetCreatedAt, + idxStackPrimaryAssetId, + uQRemoteAssetsOwnerChecksum, + uQRemoteAssetsOwnerLibraryChecksum, + idxRemoteAssetChecksum, + idxRemoteAssetStackId, + idxRemoteAssetOwnerVisibilityDeletedCreated, + authUserEntity, + userMetadataEntity, + partnerEntity, + remoteExifEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + remoteAssetCloudIdEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + assetFaceEntity, + storeEntity, + trashedLocalAssetEntity, + assetEditEntity, + settings, + assetOcrEntity, + idxPartnerSharedWithId, + idxLatLng, + idxRemoteExifCity, + idxRemoteAlbumAssetAlbumAsset, + idxRemoteAssetCloudId, + idxPersonOwnerId, + idxAssetFacePersonId, + idxAssetFaceAssetId, + idxAssetFaceVisiblePerson, + idxTrashedLocalAssetChecksum, + idxTrashedLocalAssetAlbum, + idxAssetEditAssetId, + idxAssetOcrAssetId, + ]; + late final Shape33 userEntity = Shape33( + source: i0.VersionedTable( + entityName: 'user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_108, + _column_109, + _column_110, + _column_111, + _column_112, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape50 remoteAssetEntity = Shape50( + source: i0.VersionedTable( + entityName: 'remote_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_108, + _column_113, + _column_114, + _column_115, + _column_116, + _column_117, + _column_118, + _column_107, + _column_119, + _column_120, + _column_121, + _column_122, + _column_123, + _column_124, + _column_212, + _column_125, + _column_126, + _column_127, + _column_128, + _column_129, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape35 stackEntity = Shape35( + source: i0.VersionedTable( + entityName: 'stack_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_114, + _column_115, + _column_121, + _column_130, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape36 localAssetEntity = Shape36( + source: i0.VersionedTable( + entityName: 'local_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_108, + _column_113, + _column_114, + _column_115, + _column_116, + _column_117, + _column_118, + _column_107, + _column_131, + _column_120, + _column_132, + _column_133, + _column_134, + _column_135, + _column_136, + _column_137, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape48 remoteAlbumEntity = Shape48( + source: i0.VersionedTable( + entityName: 'remote_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_108, + _column_138, + _column_114, + _column_115, + _column_139, + _column_140, + _column_141, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape38 localAlbumEntity = Shape38( + source: i0.VersionedTable( + entityName: 'local_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_108, + _column_115, + _column_142, + _column_143, + _column_144, + _column_145, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape39 localAlbumAssetEntity = Shape39( + source: i0.VersionedTable( + entityName: 'local_album_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, album_id)'], + columns: [_column_146, _column_147, _column_145], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Index idxLocalAlbumAssetAlbumAsset = i1.Index( + 'idx_local_album_asset_album_asset', + 'CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)', + ); + final i1.Index idxLocalAssetChecksum = i1.Index( + 'idx_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)', + ); + final i1.Index idxLocalAssetCloudId = i1.Index( + 'idx_local_asset_cloud_id', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)', + ); + final i1.Index idxLocalAssetCreatedAt = i1.Index( + 'idx_local_asset_created_at', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_created_at ON local_asset_entity (created_at)', + ); + final i1.Index idxStackPrimaryAssetId = i1.Index( + 'idx_stack_primary_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)', + ); + final i1.Index uQRemoteAssetsOwnerChecksum = i1.Index( + 'UQ_remote_assets_owner_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)', + ); + final i1.Index uQRemoteAssetsOwnerLibraryChecksum = i1.Index( + 'UQ_remote_assets_owner_library_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)', + ); + final i1.Index idxRemoteAssetChecksum = i1.Index( + 'idx_remote_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)', + ); + final i1.Index idxRemoteAssetStackId = i1.Index( + 'idx_remote_asset_stack_id', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)', + ); + final i1.Index idxRemoteAssetOwnerVisibilityDeletedCreated = i1.Index( + 'idx_remote_asset_owner_visibility_deleted_created', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_visibility_deleted_created ON remote_asset_entity (owner_id, visibility, deleted_at, created_at DESC)', + ); + late final Shape40 authUserEntity = Shape40( + source: i0.VersionedTable( + entityName: 'auth_user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_108, + _column_109, + _column_148, + _column_110, + _column_111, + _column_149, + _column_150, + _column_151, + _column_152, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape4 userMetadataEntity = Shape4( + source: i0.VersionedTable( + entityName: 'user_metadata_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(user_id, "key")'], + columns: [_column_153, _column_154, _column_155], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape41 partnerEntity = Shape41( + source: i0.VersionedTable( + entityName: 'partner_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(shared_by_id, shared_with_id)'], + columns: [_column_156, _column_157, _column_158], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape42 remoteExifEntity = Shape42( + source: i0.VersionedTable( + entityName: 'remote_exif_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id)'], + columns: [ + _column_159, + _column_160, + _column_161, + _column_162, + _column_163, + _column_164, + _column_117, + _column_116, + _column_165, + _column_166, + _column_167, + _column_168, + _column_135, + _column_136, + _column_169, + _column_170, + _column_171, + _column_172, + _column_173, + _column_174, + _column_175, + _column_176, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape7 remoteAlbumAssetEntity = Shape7( + source: i0.VersionedTable( + entityName: 'remote_album_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, album_id)'], + columns: [_column_159, _column_177], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape10 remoteAlbumUserEntity = Shape10( + source: i0.VersionedTable( + entityName: 'remote_album_user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(album_id, user_id)'], + columns: [_column_177, _column_153, _column_178], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape43 remoteAssetCloudIdEntity = Shape43( + source: i0.VersionedTable( + entityName: 'remote_asset_cloud_id_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id)'], + columns: [ + _column_159, + _column_179, + _column_180, + _column_134, + _column_135, + _column_136, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape44 memoryEntity = Shape44( + source: i0.VersionedTable( + entityName: 'memory_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_114, + _column_115, + _column_124, + _column_121, + _column_113, + _column_181, + _column_182, + _column_183, + _column_184, + _column_185, + _column_186, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape12 memoryAssetEntity = Shape12( + source: i0.VersionedTable( + entityName: 'memory_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, memory_id)'], + columns: [_column_159, _column_187], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape45 personEntity = Shape45( + source: i0.VersionedTable( + entityName: 'person_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_114, + _column_115, + _column_121, + _column_108, + _column_188, + _column_189, + _column_190, + _column_191, + _column_192, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape46 assetFaceEntity = Shape46( + source: i0.VersionedTable( + entityName: 'asset_face_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_159, + _column_193, + _column_194, + _column_195, + _column_196, + _column_197, + _column_198, + _column_199, + _column_200, + _column_201, + _column_124, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape18 storeEntity = Shape18( + source: i0.VersionedTable( + entityName: 'store_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [_column_202, _column_203, _column_204], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape47 trashedLocalAssetEntity = Shape47( + source: i0.VersionedTable( + entityName: 'trashed_local_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id, album_id)'], + columns: [ + _column_108, + _column_113, + _column_114, + _column_115, + _column_116, + _column_117, + _column_118, + _column_107, + _column_205, + _column_131, + _column_120, + _column_132, + _column_206, + _column_137, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape32 assetEditEntity = Shape32( + source: i0.VersionedTable( + entityName: 'asset_edit_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_159, + _column_207, + _column_208, + _column_209, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape49 settings = Shape49( + source: i0.VersionedTable( + entityName: 'settings', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY("key")'], + columns: [_column_210, _column_211, _column_115], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape51 assetOcrEntity = Shape51( + source: i0.VersionedTable( + entityName: 'asset_ocr_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_159, + _column_213, + _column_214, + _column_215, + _column_216, + _column_217, + _column_218, + _column_219, + _column_220, + _column_221, + _column_222, + _column_223, + _column_201, + ], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Index idxPartnerSharedWithId = i1.Index( + 'idx_partner_shared_with_id', + 'CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)', + ); + final i1.Index idxLatLng = i1.Index( + 'idx_lat_lng', + 'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)', + ); + final i1.Index idxRemoteExifCity = i1.Index( + 'idx_remote_exif_city', + 'CREATE INDEX IF NOT EXISTS idx_remote_exif_city ON remote_exif_entity (city) WHERE city IS NOT NULL', + ); + final i1.Index idxRemoteAlbumAssetAlbumAsset = i1.Index( + 'idx_remote_album_asset_album_asset', + 'CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)', + ); + final i1.Index idxRemoteAssetCloudId = i1.Index( + 'idx_remote_asset_cloud_id', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)', + ); + final i1.Index idxPersonOwnerId = i1.Index( + 'idx_person_owner_id', + 'CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)', + ); + final i1.Index idxAssetFacePersonId = i1.Index( + 'idx_asset_face_person_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)', + ); + final i1.Index idxAssetFaceAssetId = i1.Index( + 'idx_asset_face_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)', + ); + final i1.Index idxAssetFaceVisiblePerson = i1.Index( + 'idx_asset_face_visible_person', + 'CREATE INDEX IF NOT EXISTS idx_asset_face_visible_person ON asset_face_entity (person_id, asset_id) WHERE is_visible = 1 AND deleted_at IS NULL', + ); + final i1.Index idxTrashedLocalAssetChecksum = i1.Index( + 'idx_trashed_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)', + ); + final i1.Index idxTrashedLocalAssetAlbum = i1.Index( + 'idx_trashed_local_asset_album', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)', + ); + final i1.Index idxAssetEditAssetId = i1.Index( + 'idx_asset_edit_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_edit_asset_id ON asset_edit_entity (asset_id)', + ); + final i1.Index idxAssetOcrAssetId = i1.Index( + 'idx_asset_ocr_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_ocr_asset_id ON asset_ocr_entity (asset_id)', + ); +} + +class Shape51 extends i0.VersionedTable { + Shape51({required super.source, required super.alias}) : super.aliased(); i1.GeneratedColumn get id => columnsByName['id']! as i1.GeneratedColumn; i1.GeneratedColumn get assetId => @@ -12964,7 +15243,7 @@ class Shape49 extends i0.VersionedTable { columnsByName['is_visible']! as i1.GeneratedColumn; } -i1.GeneratedColumn _column_210(String aliasedName) => +i1.GeneratedColumn _column_213(String aliasedName) => i1.GeneratedColumn( 'x1', aliasedName, @@ -12972,7 +15251,7 @@ i1.GeneratedColumn _column_210(String aliasedName) => type: i1.DriftSqlType.double, $customConstraints: 'NOT NULL', ); -i1.GeneratedColumn _column_211(String aliasedName) => +i1.GeneratedColumn _column_214(String aliasedName) => i1.GeneratedColumn( 'y1', aliasedName, @@ -12980,7 +15259,7 @@ i1.GeneratedColumn _column_211(String aliasedName) => type: i1.DriftSqlType.double, $customConstraints: 'NOT NULL', ); -i1.GeneratedColumn _column_212(String aliasedName) => +i1.GeneratedColumn _column_215(String aliasedName) => i1.GeneratedColumn( 'x2', aliasedName, @@ -12988,7 +15267,7 @@ i1.GeneratedColumn _column_212(String aliasedName) => type: i1.DriftSqlType.double, $customConstraints: 'NOT NULL', ); -i1.GeneratedColumn _column_213(String aliasedName) => +i1.GeneratedColumn _column_216(String aliasedName) => i1.GeneratedColumn( 'y2', aliasedName, @@ -12996,7 +15275,7 @@ i1.GeneratedColumn _column_213(String aliasedName) => type: i1.DriftSqlType.double, $customConstraints: 'NOT NULL', ); -i1.GeneratedColumn _column_214(String aliasedName) => +i1.GeneratedColumn _column_217(String aliasedName) => i1.GeneratedColumn( 'x3', aliasedName, @@ -13004,7 +15283,7 @@ i1.GeneratedColumn _column_214(String aliasedName) => type: i1.DriftSqlType.double, $customConstraints: 'NOT NULL', ); -i1.GeneratedColumn _column_215(String aliasedName) => +i1.GeneratedColumn _column_218(String aliasedName) => i1.GeneratedColumn( 'y3', aliasedName, @@ -13012,7 +15291,7 @@ i1.GeneratedColumn _column_215(String aliasedName) => type: i1.DriftSqlType.double, $customConstraints: 'NOT NULL', ); -i1.GeneratedColumn _column_216(String aliasedName) => +i1.GeneratedColumn _column_219(String aliasedName) => i1.GeneratedColumn( 'x4', aliasedName, @@ -13020,7 +15299,7 @@ i1.GeneratedColumn _column_216(String aliasedName) => type: i1.DriftSqlType.double, $customConstraints: 'NOT NULL', ); -i1.GeneratedColumn _column_217(String aliasedName) => +i1.GeneratedColumn _column_220(String aliasedName) => i1.GeneratedColumn( 'y4', aliasedName, @@ -13028,7 +15307,7 @@ i1.GeneratedColumn _column_217(String aliasedName) => type: i1.DriftSqlType.double, $customConstraints: 'NOT NULL', ); -i1.GeneratedColumn _column_218(String aliasedName) => +i1.GeneratedColumn _column_221(String aliasedName) => i1.GeneratedColumn( 'box_score', aliasedName, @@ -13036,7 +15315,7 @@ i1.GeneratedColumn _column_218(String aliasedName) => type: i1.DriftSqlType.double, $customConstraints: 'NOT NULL', ); -i1.GeneratedColumn _column_219(String aliasedName) => +i1.GeneratedColumn _column_222(String aliasedName) => i1.GeneratedColumn( 'text_score', aliasedName, @@ -13044,7 +15323,7 @@ i1.GeneratedColumn _column_219(String aliasedName) => type: i1.DriftSqlType.double, $customConstraints: 'NOT NULL', ); -i1.GeneratedColumn _column_220(String aliasedName) => +i1.GeneratedColumn _column_223(String aliasedName) => i1.GeneratedColumn( 'recognized_text', aliasedName, @@ -13077,6 +15356,10 @@ i0.MigrationStepWithVersion migrationSteps({ required Future Function(i1.Migrator m, Schema23 schema) from22To23, required Future Function(i1.Migrator m, Schema24 schema) from23To24, required Future Function(i1.Migrator m, Schema25 schema) from24To25, + required Future Function(i1.Migrator m, Schema26 schema) from25To26, + required Future Function(i1.Migrator m, Schema27 schema) from26To27, + required Future Function(i1.Migrator m, Schema28 schema) from27To28, + required Future Function(i1.Migrator m, Schema29 schema) from28To29, }) { return (currentVersion, database) async { switch (currentVersion) { @@ -13200,6 +15483,26 @@ i0.MigrationStepWithVersion migrationSteps({ final migrator = i1.Migrator(database, schema); await from24To25(migrator, schema); return 25; + case 25: + final schema = Schema26(database: database); + final migrator = i1.Migrator(database, schema); + await from25To26(migrator, schema); + return 26; + case 26: + final schema = Schema27(database: database); + final migrator = i1.Migrator(database, schema); + await from26To27(migrator, schema); + return 27; + case 27: + final schema = Schema28(database: database); + final migrator = i1.Migrator(database, schema); + await from27To28(migrator, schema); + return 28; + case 28: + final schema = Schema29(database: database); + final migrator = i1.Migrator(database, schema); + await from28To29(migrator, schema); + return 29; default: throw ArgumentError.value('Unknown migration from $currentVersion'); } @@ -13231,6 +15534,10 @@ i1.OnUpgrade stepByStep({ required Future Function(i1.Migrator m, Schema23 schema) from22To23, required Future Function(i1.Migrator m, Schema24 schema) from23To24, required Future Function(i1.Migrator m, Schema25 schema) from24To25, + required Future Function(i1.Migrator m, Schema26 schema) from25To26, + required Future Function(i1.Migrator m, Schema27 schema) from26To27, + required Future Function(i1.Migrator m, Schema28 schema) from27To28, + required Future Function(i1.Migrator m, Schema29 schema) from28To29, }) => i0.VersionedSchema.stepByStepHelper( step: migrationSteps( from1To2: from1To2, @@ -13257,5 +15564,9 @@ i1.OnUpgrade stepByStep({ from22To23: from22To23, from23To24: from23To24, from24To25: from24To25, + from25To26: from25To26, + from26To27: from26To27, + from27To28: from27To28, + from28To29: from28To29, ), ); diff --git a/mobile/lib/infrastructure/repositories/local_album.repository.dart b/mobile/lib/infrastructure/repositories/local_album.repository.dart index 9b355334d4..2c80385c34 100644 --- a/mobile/lib/infrastructure/repositories/local_album.repository.dart +++ b/mobile/lib/infrastructure/repositories/local_album.repository.dart @@ -241,7 +241,7 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository { innerJoin(_db.localAssetEntity, _db.localAlbumAssetEntity.assetId.equalsExp(_db.localAssetEntity.id)), ]) ..where(_db.localAlbumAssetEntity.albumId.equals(albumId) & _db.localAssetEntity.checksum.isNull()) - ..orderBy([OrderingTerm.asc(_db.localAssetEntity.id)]); + ..orderBy([OrderingTerm.desc(_db.localAssetEntity.createdAt)]); return query.map((row) => row.readTable(_db.localAssetEntity).toDto()).get(); } diff --git a/mobile/lib/infrastructure/repositories/remote_album.repository.dart b/mobile/lib/infrastructure/repositories/remote_album.repository.dart index 19ebcaac45..71d96bb0cb 100644 --- a/mobile/lib/infrastructure/repositories/remote_album.repository.dart +++ b/mobile/lib/infrastructure/repositories/remote_album.repository.dart @@ -10,6 +10,7 @@ import 'package:immich_mobile/infrastructure/entities/remote_album.entity.drift. import 'package:immich_mobile/infrastructure/entities/remote_album_asset.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/remote_album_user.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; enum SortRemoteAlbumsBy { id, updatedAt } @@ -159,7 +160,7 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository { createdAt: Value(album.createdAt), updatedAt: Value(album.updatedAt), description: Value(album.description), - thumbnailAssetId: Value(album.thumbnailAssetId), + thumbnailAssetId: Value(album.thumbnailAssetId ?? (assetIds.isNotEmpty ? assetIds.first : null)), isActivityEnabled: Value(album.isActivityEnabled), order: Value(album.order), ); @@ -274,17 +275,59 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository { } Future addAssets(String albumId, List assetIds) async { + if (assetIds.isEmpty) { + return 0; + } + final albumAssets = assetIds.map( (assetId) => RemoteAlbumAssetEntityCompanion(albumId: Value(albumId), assetId: Value(assetId)), ); - await _db.batch((batch) { - batch.insertAll(_db.remoteAlbumAssetEntity, albumAssets); + await _db.transaction(() async { + await _db.batch((batch) { + batch.insertAll(_db.remoteAlbumAssetEntity, albumAssets); + }); + + final album = _db.update(_db.remoteAlbumEntity) + ..where((row) => row.id.equals(albumId) & row.thumbnailAssetId.isNull()); + + await album.write(RemoteAlbumEntityCompanion(thumbnailAssetId: Value(assetIds.first))); }); return assetIds.length; } + /// Inserts a placeholder `remote_asset_entity` row from a freshly-uploaded + /// local asset. Skips silently if a row with the same id or + /// (owner_id, checksum) already exists โ€” sync will overwrite with the + /// authoritative server data once the AssetUploadReadyV1 event is processed. + Future upsertRemoteAssetStub({ + required String remoteId, + required String ownerId, + required LocalAsset source, + }) async { + await _db + .into(_db.remoteAssetEntity) + .insert( + RemoteAssetEntityCompanion( + id: Value(remoteId), + ownerId: Value(ownerId), + checksum: Value(source.checksum ?? remoteId), + name: Value(source.name), + type: Value(source.type), + createdAt: Value(source.createdAt), + updatedAt: Value(source.updatedAt), + width: Value(source.width), + height: Value(source.height), + durationMs: Value(source.durationMs), + isFavorite: Value(source.isFavorite), + visibility: const Value(AssetVisibility.timeline), + isEdited: Value(source.isEdited), + ), + mode: InsertMode.insertOrIgnore, + ); + } + Future addUsers(String albumId, List userIds) { final albumUsers = userIds.map( (assetId) => RemoteAlbumUserEntityCompanion( @@ -360,7 +403,9 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository { } Future> getSortedAlbumIds(List albumIds, {required AssetDateAggregation aggregation}) async { - if (albumIds.isEmpty) return []; + if (albumIds.isEmpty) { + return []; + } final jsonIds = jsonEncode(albumIds); final sqlAgg = aggregation == AssetDateAggregation.start ? 'MIN' : 'MAX'; diff --git a/mobile/lib/infrastructure/repositories/remote_asset.repository.dart b/mobile/lib/infrastructure/repositories/remote_asset.repository.dart index 6d19d17931..7d4e23c22b 100644 --- a/mobile/lib/infrastructure/repositories/remote_asset.repository.dart +++ b/mobile/lib/infrastructure/repositories/remote_asset.repository.dart @@ -164,6 +164,16 @@ class RemoteAssetRepository extends DriftDatabaseRepository { }); } + Future emptyTrash(String ownerId) async { + await _db.remoteAssetEntity.deleteWhere((t) => t.deletedAt.isNotNull() & t.ownerId.equals(ownerId)); + } + + Future restoreAllTrash(String ownerId) async { + await (_db.remoteAssetEntity.update()..where((t) => t.deletedAt.isNotNull() & t.ownerId.equals(ownerId))).write( + const RemoteAssetEntityCompanion(deletedAt: Value(null)), + ); + } + Future delete(List ids) { return _db.batch((batch) { for (final id in ids) { diff --git a/mobile/lib/infrastructure/repositories/search_api.repository.dart b/mobile/lib/infrastructure/repositories/search_api.repository.dart index bcfddfce6e..e2f2af17eb 100644 --- a/mobile/lib/infrastructure/repositories/search_api.repository.dart +++ b/mobile/lib/infrastructure/repositories/search_api.repository.dart @@ -20,50 +20,64 @@ class SearchApiRepository extends ApiRepository { (filter.assetId != null && filter.assetId!.isNotEmpty)) { return _api.searchSmart( SmartSearchDto( - query: filter.context, - queryAssetId: filter.assetId, - language: filter.language, - country: filter.location.country, - state: filter.location.state, - city: filter.location.city, - make: filter.camera.make, - model: filter.camera.model, - takenAfter: filter.date.takenAfter, - takenBefore: filter.date.takenBefore, - visibility: filter.display.isArchive ? AssetVisibility.archive : AssetVisibility.timeline, - rating: filter.rating.rating, - isFavorite: filter.display.isFavorite ? true : null, - isNotInAlbum: filter.display.isNotInAlbum ? true : null, - personIds: filter.people.map((e) => e.id).toList(), - tagIds: filter.tagIds, - type: type, - page: page, - size: 100, + query: filter.context == null ? const Optional.absent() : Optional.present(filter.context!), + queryAssetId: filter.assetId == null ? const Optional.absent() : Optional.present(filter.assetId!), + language: filter.language == null ? const Optional.absent() : Optional.present(filter.language!), + country: filter.location.country == null + ? const Optional.absent() + : Optional.present(filter.location.country!), + state: filter.location.state == null ? const Optional.absent() : Optional.present(filter.location.state!), + city: filter.location.city == null ? const Optional.absent() : Optional.present(filter.location.city!), + make: filter.camera.make == null ? const Optional.absent() : Optional.present(filter.camera.make!), + model: filter.camera.model == null ? const Optional.absent() : Optional.present(filter.camera.model!), + takenAfter: filter.date.takenAfter == null + ? const Optional.absent() + : Optional.present(filter.date.takenAfter!), + takenBefore: filter.date.takenBefore == null + ? const Optional.absent() + : Optional.present(filter.date.takenBefore!), + visibility: Optional.present(filter.display.isArchive ? AssetVisibility.archive : AssetVisibility.timeline), + rating: filter.rating.rating == null ? const Optional.absent() : Optional.present(filter.rating.rating!), + isFavorite: filter.display.isFavorite ? const Optional.present(true) : const Optional.absent(), + isNotInAlbum: filter.display.isNotInAlbum ? const Optional.present(true) : const Optional.absent(), + personIds: Optional.present(filter.people.map((e) => e.id).toList()), + tagIds: filter.tagIds == null ? const Optional.absent() : Optional.present(filter.tagIds!), + type: type == null ? const Optional.absent() : Optional.present(type), + page: Optional.present(page), + size: const Optional.present(100), ), ); } return _api.searchAssets( MetadataSearchDto( - originalFileName: filter.filename != null && filter.filename!.isNotEmpty ? filter.filename : null, - country: filter.location.country, - description: filter.description != null && filter.description!.isNotEmpty ? filter.description : null, - ocr: filter.ocr != null && filter.ocr!.isNotEmpty ? filter.ocr : null, - state: filter.location.state, - city: filter.location.city, - make: filter.camera.make, - model: filter.camera.model, - takenAfter: filter.date.takenAfter, - takenBefore: filter.date.takenBefore, - visibility: filter.display.isArchive ? AssetVisibility.archive : AssetVisibility.timeline, - rating: filter.rating.rating, - isFavorite: filter.display.isFavorite ? true : null, - isNotInAlbum: filter.display.isNotInAlbum ? true : null, - personIds: filter.people.map((e) => e.id).toList(), - tagIds: filter.tagIds, - type: type, - page: page, - size: 1000, + originalFileName: filter.filename != null && filter.filename!.isNotEmpty + ? Optional.present(filter.filename!) + : const Optional.absent(), + country: filter.location.country == null ? const Optional.absent() : Optional.present(filter.location.country!), + description: filter.description != null && filter.description!.isNotEmpty + ? Optional.present(filter.description!) + : const Optional.absent(), + ocr: filter.ocr != null && filter.ocr!.isNotEmpty ? Optional.present(filter.ocr!) : const Optional.absent(), + state: filter.location.state == null ? const Optional.absent() : Optional.present(filter.location.state!), + city: filter.location.city == null ? const Optional.absent() : Optional.present(filter.location.city!), + make: filter.camera.make == null ? const Optional.absent() : Optional.present(filter.camera.make!), + model: filter.camera.model == null ? const Optional.absent() : Optional.present(filter.camera.model!), + takenAfter: filter.date.takenAfter == null + ? const Optional.absent() + : Optional.present(filter.date.takenAfter!), + takenBefore: filter.date.takenBefore == null + ? const Optional.absent() + : Optional.present(filter.date.takenBefore!), + visibility: Optional.present(filter.display.isArchive ? AssetVisibility.archive : AssetVisibility.timeline), + rating: filter.rating.rating == null ? const Optional.absent() : Optional.present(filter.rating.rating!), + isFavorite: filter.display.isFavorite ? const Optional.present(true) : const Optional.absent(), + isNotInAlbum: filter.display.isNotInAlbum ? const Optional.present(true) : const Optional.absent(), + personIds: Optional.present(filter.people.map((e) => e.id).toList()), + tagIds: filter.tagIds == null ? const Optional.absent() : Optional.present(filter.tagIds!), + type: type == null ? const Optional.absent() : Optional.present(type), + page: Optional.present(page), + size: const Optional.present(1000), ), ); } diff --git a/mobile/lib/infrastructure/repositories/settings.repository.dart b/mobile/lib/infrastructure/repositories/settings.repository.dart new file mode 100644 index 0000000000..56066f543a --- /dev/null +++ b/mobile/lib/infrastructure/repositories/settings.repository.dart @@ -0,0 +1,84 @@ +import 'package:collection/collection.dart'; +import 'package:drift/drift.dart'; +import 'package:immich_mobile/domain/models/config/app_config.dart'; +import 'package:immich_mobile/domain/models/settings_key.dart'; +import 'package:immich_mobile/infrastructure/entities/settings.entity.drift.dart'; +import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; + +class SettingsRepository extends DriftDatabaseRepository { + final Drift _db; + + SettingsRepository._(this._db) : super(_db); + + static SettingsRepository? _instance; + + static SettingsRepository get instance { + final instance = _instance; + if (instance == null) { + throw StateError('SettingsRepository not initialized. Call ensureInitialized() first'); + } + return instance; + } + + AppConfig _appConfig = const .new(); + AppConfig get appConfig => _appConfig; + + static Future ensureInitialized(Drift db) async { + if (_instance == null) { + final instance = SettingsRepository._(db); + await instance.refresh(); + _instance = instance; + } + return _instance!; + } + + Future refresh() async => _applyOverrides(await _db.select(_db.settingsEntity).get()); + + Future clear(Iterable keys) async { + if (keys.isEmpty) { + return; + } + + final names = keys.map((key) => key.name).toList(); + await (_db.delete(_db.settingsEntity)..where((row) => row.key.isIn(names))).go(); + + for (final key in keys) { + _appConfig = _appConfig.write(key, defaultConfig.read(key)); + } + } + + Future write(SettingsKey key, U value) async { + if (value == _appConfig.read(key)) { + return; + } + + if (value == defaultConfig.read(key)) { + return clear([key]); + } + + await _db + .into(_db.settingsEntity) + .insertOnConflictUpdate( + SettingsEntityCompanion.insert(key: key.name, value: key.encode(value), updatedAt: Value(DateTime.now())), + ); + _appConfig = _appConfig.write(key, value); + } + + Stream watchConfig() => _db.select(_db.settingsEntity).watch().map((rows) { + _applyOverrides(rows); + return _appConfig; + }); + + void _applyOverrides(List rows) { + _appConfig = AppConfig.fromEntries( + rows.fold({}, (overrides, row) { + final metadataKey = SettingsKey.values.firstWhereOrNull((key) => key.name == row.key); + if (metadataKey == null) { + return overrides; + } + + return {...overrides, metadataKey: metadataKey.decode(row.value)}; + }), + ); + } +} diff --git a/mobile/lib/infrastructure/repositories/sync_api.repository.dart b/mobile/lib/infrastructure/repositories/sync_api.repository.dart index fc87c90f90..e9d57f7506 100644 --- a/mobile/lib/infrastructure/repositories/sync_api.repository.dart +++ b/mobile/lib/infrastructure/repositories/sync_api.repository.dart @@ -3,9 +3,7 @@ import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:immich_mobile/constants/constants.dart'; -import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/models/sync_event.model.dart'; -import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/infrastructure/repositories/network.repository.dart'; import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/utils/semver.dart'; @@ -22,7 +20,7 @@ class SyncApiRepository { } Future deleteSyncAck(List types) { - return _api.syncApi.deleteSyncAck(SyncAckDeleteDto(types: types)); + return _api.syncApi.deleteSyncAck(SyncAckDeleteDto(types: Optional.present(types))); } Future streamChanges( @@ -31,6 +29,7 @@ class SyncApiRepository { Function()? onReset, int batchSize = kSyncEventBatchSize, http.Client? httpClient, + Future? abortSignal, }) async { final stopwatch = Stopwatch()..start(); final client = httpClient ?? NetworkRepository.client; @@ -38,8 +37,7 @@ class SyncApiRepository { final headers = {'Content-Type': 'application/json', 'Accept': 'application/jsonlines+json'}; - final shouldReset = Store.get(StoreKey.shouldResetSync, false); - final request = http.Request('POST', Uri.parse(endpoint)); + final request = http.AbortableRequest('POST', Uri.parse(endpoint), abortTrigger: abortSignal); request.headers.addAll(headers); request.body = jsonEncode( SyncStreamDto( @@ -78,7 +76,6 @@ class SyncApiRepository { : SyncRequestType.assetFacesV1, if (serverVersion >= const SemVer(major: 3, minor: 0, patch: 0)) SyncRequestType.assetOcrV1, ], - reset: shouldReset, ).toJson(), ); @@ -102,9 +99,6 @@ class SyncApiRepository { throw ApiException(response.statusCode, 'Failed to get sync stream: $errorBody'); } - // Reset after successful stream start - await Store.put(StoreKey.shouldResetSync, false); - await for (final chunk in response.stream.transform(utf8.decoder)) { if (shouldAbort) { break; diff --git a/mobile/lib/infrastructure/repositories/sync_stream.repository.dart b/mobile/lib/infrastructure/repositories/sync_stream.repository.dart index 1f30c7fce1..8de76f1412 100644 --- a/mobile/lib/infrastructure/repositories/sync_stream.repository.dart +++ b/mobile/lib/infrastructure/repositories/sync_stream.repository.dart @@ -15,6 +15,7 @@ import 'package:immich_mobile/infrastructure/entities/asset_face.entity.drift.da import 'package:immich_mobile/infrastructure/entities/asset_ocr.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/auth_user.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/exif.entity.drift.dart'; +import 'package:immich_mobile/infrastructure/entities/local_album.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/memory.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/memory_asset.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/partner.entity.drift.dart'; @@ -46,26 +47,36 @@ class SyncStreamRepository extends DriftDatabaseRepository { // foreign_keys PRAGMA is no-op within transactions // https://www.sqlite.org/pragma.html#pragma_foreign_keys await _db.customStatement('PRAGMA foreign_keys = OFF'); - await transaction(() async { - await _db.assetFaceEntity.deleteAll(); - await _db.memoryAssetEntity.deleteAll(); - await _db.memoryEntity.deleteAll(); - await _db.partnerEntity.deleteAll(); - await _db.personEntity.deleteAll(); - await _db.remoteAlbumAssetEntity.deleteAll(); - await _db.remoteAlbumEntity.deleteAll(); - await _db.remoteAlbumUserEntity.deleteAll(); - await _db.remoteAssetEntity.deleteAll(); - await _db.remoteExifEntity.deleteAll(); - await _db.stackEntity.deleteAll(); - await _db.authUserEntity.deleteAll(); - await _db.userEntity.deleteAll(); - await _db.userMetadataEntity.deleteAll(); - await _db.remoteAssetCloudIdEntity.deleteAll(); - await _db.assetEditEntity.deleteAll(); - await _db.assetOcrEntity.deleteAll(); - }); - await _db.customStatement('PRAGMA foreign_keys = ON'); + try { + await transaction(() async { + // FK cascade (ON DELETE SET NULL) does not fire while foreign_keys = OFF, + // so null linkedRemoteAlbumId manually to avoid dangling pointers in local_album_entity. + await _db.localAlbumEntity.update().write( + const LocalAlbumEntityCompanion(linkedRemoteAlbumId: Value(null)), + ); + await _db.assetFaceEntity.deleteAll(); + await _db.memoryAssetEntity.deleteAll(); + await _db.memoryEntity.deleteAll(); + await _db.partnerEntity.deleteAll(); + await _db.personEntity.deleteAll(); + await _db.remoteAlbumAssetEntity.deleteAll(); + await _db.remoteAlbumEntity.deleteAll(); + await _db.remoteAlbumUserEntity.deleteAll(); + await _db.remoteAssetEntity.deleteAll(); + await _db.remoteExifEntity.deleteAll(); + await _db.stackEntity.deleteAll(); + await _db.authUserEntity.deleteAll(); + await _db.userEntity.deleteAll(); + await _db.userMetadataEntity.deleteAll(); + await _db.remoteAssetCloudIdEntity.deleteAll(); + await _db.assetEditEntity.deleteAll(); + await _db.assetOcrEntity.deleteAll(); + }); + } finally { + // re-enable FK even if the transaction throws, otherwise the connection + // would be left with foreign_keys = OFF, silently disabling cascades. + await _db.customStatement('PRAGMA foreign_keys = ON'); + } }); } catch (error, stack) { _logger.severe('Error: SyncResetV1', error, stack); @@ -82,7 +93,7 @@ class SyncStreamRepository extends DriftDatabaseRepository { email: Value(user.email), hasProfileImage: Value(user.hasProfileImage), profileChangedAt: Value(user.profileChangedAt), - avatarColor: Value(user.avatarColor?.toAvatarColor() ?? AvatarColor.primary), + avatarColor: Value(user.avatarColor.orElse(null)?.toAvatarColor() ?? AvatarColor.primary), isAdmin: Value(user.isAdmin), pinCode: Value(user.pinCode), quotaSizeInBytes: Value(user.quotaSizeInBytes ?? 0), @@ -124,7 +135,7 @@ class SyncStreamRepository extends DriftDatabaseRepository { email: Value(user.email), hasProfileImage: Value(user.hasProfileImage), profileChangedAt: Value(user.profileChangedAt), - avatarColor: Value(user.avatarColor?.toAvatarColor() ?? AvatarColor.primary), + avatarColor: Value(user.avatarColor.orElse(null)?.toAvatarColor() ?? AvatarColor.primary), ); batch.insert(_db.userEntity, companion.copyWith(id: Value(user.id)), onConflict: DoUpdate((_) => companion)); @@ -193,6 +204,7 @@ class SyncStreamRepository extends DriftDatabaseRepository { type: Value(asset.type.toAssetType()), createdAt: Value.absentIfNull(asset.fileCreatedAt), updatedAt: Value.absentIfNull(asset.fileModifiedAt), + uploadedAt: Value(asset.createdAt), durationMs: Value(asset.duration?.toDuration()?.inMilliseconds ?? 0), checksum: Value(asset.checksum), isFavorite: Value(asset.isFavorite), @@ -231,6 +243,7 @@ class SyncStreamRepository extends DriftDatabaseRepository { type: Value(asset.type.toAssetType()), createdAt: Value.absentIfNull(asset.fileCreatedAt), updatedAt: Value.absentIfNull(asset.fileModifiedAt), + uploadedAt: Value(asset.createdAt), durationMs: Value(asset.duration), checksum: Value(asset.checksum), isFavorite: Value(asset.isFavorite), diff --git a/mobile/lib/infrastructure/repositories/tags_api.repository.dart b/mobile/lib/infrastructure/repositories/tags_api.repository.dart index e81b79c459..5963fc2f23 100644 --- a/mobile/lib/infrastructure/repositories/tags_api.repository.dart +++ b/mobile/lib/infrastructure/repositories/tags_api.repository.dart @@ -14,4 +14,13 @@ class TagsApiRepository extends ApiRepository { Future?> getAllTags() async { return await _api.getAllTags(); } + + Future bulkTagAssets(List assetIds, List tagIds) async { + final response = await _api.bulkTagAssets(TagBulkAssetsDto(assetIds: assetIds, tagIds: tagIds)); + return response?.count ?? 0; + } + + Future?> upsertTags(List tags) async { + return _api.upsertTags(TagUpsertDto(tags: tags)); + } } diff --git a/mobile/lib/infrastructure/repositories/timeline.repository.dart b/mobile/lib/infrastructure/repositories/timeline.repository.dart index 65788f07e2..9c4501b665 100644 --- a/mobile/lib/infrastructure/repositories/timeline.repository.dart +++ b/mobile/lib/infrastructure/repositories/timeline.repository.dart @@ -79,6 +79,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository { type: row.type, createdAt: row.createdAt, updatedAt: row.updatedAt, + uploadedAt: row.uploadedAt, thumbHash: row.thumbHash, width: row.width, height: row.height, @@ -244,17 +245,21 @@ class DriftTimelineRepository extends DriftDatabaseRepository { final isAscending = albumData.order == AlbumAssetOrder.asc; - final query = _db.remoteAssetEntity.select().addColumns([_db.localAssetEntity.id]).join([ + // Correlated subquery picks the first matching local asset by checksum, + // avoiding fan-out when the same photo exists in multiple device albums (#23273). + final localId = subqueryExpression( + _db.localAssetEntity.selectOnly() + ..addColumns([_db.localAssetEntity.id]) + ..where(_db.localAssetEntity.checksum.equalsExp(_db.remoteAssetEntity.checksum)) + ..limit(1), + ); + + final query = _db.remoteAssetEntity.select().addColumns([localId]).join([ innerJoin( _db.remoteAlbumAssetEntity, _db.remoteAlbumAssetEntity.assetId.equalsExp(_db.remoteAssetEntity.id), useColumns: false, ), - leftOuterJoin( - _db.localAssetEntity, - _db.remoteAssetEntity.checksum.equalsExp(_db.localAssetEntity.checksum), - useColumns: false, - ), ])..where(_db.remoteAssetEntity.deletedAt.isNull() & _db.remoteAlbumAssetEntity.albumId.equals(albumId)); if (isAscending) { @@ -265,9 +270,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository { query.limit(count, offset: offset); - return query - .map((row) => row.readTable(_db.remoteAssetEntity).toDto(localId: row.read(_db.localAssetEntity.id))) - .get(); + return query.map((row) => row.readTable(_db.remoteAssetEntity).toDto(localId: row.read(localId))).get(); } TimelineQuery fromAssets(List assets, TimelineOrigin origin) => ( @@ -315,6 +318,17 @@ class DriftTimelineRepository extends DriftDatabaseRepository { origin: TimelineOrigin.remoteAssets, ); + TimelineQuery recentlyAdded(String userId, GroupAssetsBy groupBy) => _remoteQueryBuilder( + filter: (row) => + row.uploadedAt.isNotNull() & + row.deletedAt.isNull() & + row.ownerId.equals(userId) & + (row.visibility.equalsValue(AssetVisibility.timeline) | row.visibility.equalsValue(AssetVisibility.archive)), + origin: TimelineOrigin.recentlyAdded, + groupBy: groupBy, + sortBy: SortAssetsBy.uploaded, + ); + TimelineQuery favorite(String userId, GroupAssetsBy groupBy) => _remoteQueryBuilder( filter: (row) => row.deletedAt.isNull() & @@ -422,23 +436,22 @@ class DriftTimelineRepository extends DriftDatabaseRepository { } Stream> _watchPersonBucket(String userId, String personId, {GroupAssetsBy groupBy = GroupAssetsBy.day}) { + final idQuery = _db.assetFaceEntity.selectOnly() + ..addColumns([_db.assetFaceEntity.assetId]) + ..where( + _db.assetFaceEntity.personId.equals(personId) & + _db.assetFaceEntity.isVisible.equals(true) & + _db.assetFaceEntity.deletedAt.isNull(), + ); + if (groupBy == GroupAssetsBy.none) { final query = _db.remoteAssetEntity.selectOnly() ..addColumns([_db.remoteAssetEntity.id.count()]) - ..join([ - innerJoin( - _db.assetFaceEntity, - _db.assetFaceEntity.assetId.equalsExp(_db.remoteAssetEntity.id), - useColumns: false, - ), - ]) ..where( - _db.remoteAssetEntity.deletedAt.isNull() & + _db.remoteAssetEntity.id.isInQuery(idQuery) & + _db.remoteAssetEntity.deletedAt.isNull() & _db.remoteAssetEntity.ownerId.equals(userId) & - _db.remoteAssetEntity.visibility.equalsValue(AssetVisibility.timeline) & - _db.assetFaceEntity.personId.equals(personId) & - _db.assetFaceEntity.isVisible.equals(true) & - _db.assetFaceEntity.deletedAt.isNull(), + _db.remoteAssetEntity.visibility.equalsValue(AssetVisibility.timeline), ); return query.map((row) { @@ -452,20 +465,11 @@ class DriftTimelineRepository extends DriftDatabaseRepository { final query = _db.remoteAssetEntity.selectOnly() ..addColumns([assetCountExp, dateExp]) - ..join([ - innerJoin( - _db.assetFaceEntity, - _db.assetFaceEntity.assetId.equalsExp(_db.remoteAssetEntity.id), - useColumns: false, - ), - ]) ..where( - _db.remoteAssetEntity.deletedAt.isNull() & + _db.remoteAssetEntity.id.isInQuery(idQuery) & _db.remoteAssetEntity.ownerId.equals(userId) & _db.remoteAssetEntity.visibility.equalsValue(AssetVisibility.timeline) & - _db.assetFaceEntity.personId.equals(personId) & - _db.assetFaceEntity.isVisible.equals(true) & - _db.assetFaceEntity.deletedAt.isNull(), + _db.remoteAssetEntity.deletedAt.isNull(), ) ..groupBy([dateExp]) ..orderBy([OrderingTerm.desc(dateExp)]); @@ -483,26 +487,26 @@ class DriftTimelineRepository extends DriftDatabaseRepository { required int offset, required int count, }) { - final query = - _db.remoteAssetEntity.select().join([ - innerJoin( - _db.assetFaceEntity, - _db.assetFaceEntity.assetId.equalsExp(_db.remoteAssetEntity.id), - useColumns: false, - ), - ]) - ..where( - _db.remoteAssetEntity.deletedAt.isNull() & - _db.remoteAssetEntity.ownerId.equals(userId) & - _db.remoteAssetEntity.visibility.equalsValue(AssetVisibility.timeline) & - _db.assetFaceEntity.personId.equals(personId) & - _db.assetFaceEntity.isVisible.equals(true) & - _db.assetFaceEntity.deletedAt.isNull(), - ) - ..orderBy([OrderingTerm.desc(_db.remoteAssetEntity.createdAt)]) - ..limit(count, offset: offset); + final idQuery = _db.assetFaceEntity.selectOnly() + ..addColumns([_db.assetFaceEntity.assetId]) + ..where( + _db.assetFaceEntity.personId.equals(personId) & + _db.assetFaceEntity.isVisible.equals(true) & + _db.assetFaceEntity.deletedAt.isNull(), + ); - return query.map((row) => row.readTable(_db.remoteAssetEntity).toDto()).get(); + final query = _db.remoteAssetEntity.select() + ..where( + (row) => + row.id.isInQuery(idQuery) & + row.deletedAt.isNull() & + row.ownerId.equals(userId) & + row.visibility.equalsValue(AssetVisibility.timeline), + ) + ..orderBy([(row) => OrderingTerm.desc(row.createdAt)]) + ..limit(count, offset: offset); + + return query.map((row) => row.toDto()).get(); } TimelineQuery map(List userIds, TimelineMapOptions options, GroupAssetsBy groupBy) => ( @@ -605,9 +609,10 @@ class DriftTimelineRepository extends DriftDatabaseRepository { required TimelineOrigin origin, GroupAssetsBy groupBy = GroupAssetsBy.day, bool joinLocal = false, + SortAssetsBy sortBy = SortAssetsBy.taken, }) { return ( - bucketSource: () => _watchRemoteBucket(filter: filter, groupBy: groupBy), + bucketSource: () => _watchRemoteBucket(filter: filter, groupBy: groupBy, sortBy: sortBy), assetSource: (offset, count) => _getRemoteAssets(filter: filter, offset: offset, count: count, joinLocal: joinLocal), origin: origin, @@ -617,6 +622,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository { Stream> _watchRemoteBucket({ required Expression Function($RemoteAssetEntityTable row) filter, GroupAssetsBy groupBy = GroupAssetsBy.day, + SortAssetsBy sortBy = SortAssetsBy.taken, }) { if (groupBy == GroupAssetsBy.none) { final query = _db.remoteAssetEntity.count(where: filter); @@ -624,7 +630,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository { } final assetCountExp = _db.remoteAssetEntity.id.count(); - final dateExp = _db.remoteAssetEntity.effectiveCreatedAt(groupBy); + final dateExp = _db.remoteAssetEntity.effectiveCreatedAt(groupBy, sortBy: sortBy); final query = _db.remoteAssetEntity.selectOnly() ..addColumns([assetCountExp, dateExp]) @@ -700,8 +706,13 @@ extension on Expression { } extension on $RemoteAssetEntityTable { - Expression effectiveCreatedAt(GroupAssetsBy groupBy) => - coalesce([localDateTime.dateFmt(groupBy), createdAt.dateFmt(groupBy, toLocal: true)]); + Expression effectiveCreatedAt(GroupAssetsBy groupBy, {SortAssetsBy sortBy = SortAssetsBy.taken}) { + if (sortBy == SortAssetsBy.uploaded) { + return uploadedAt.dateFmt(groupBy, toLocal: true); + } + + return coalesce([localDateTime.dateFmt(groupBy), createdAt.dateFmt(groupBy, toLocal: true)]); + } } extension on String { diff --git a/mobile/lib/infrastructure/repositories/user.repository.dart b/mobile/lib/infrastructure/repositories/user.repository.dart index ce7cb124db..afcf2271dd 100644 --- a/mobile/lib/infrastructure/repositories/user.repository.dart +++ b/mobile/lib/infrastructure/repositories/user.repository.dart @@ -12,7 +12,9 @@ class DriftAuthUserRepository extends DriftDatabaseRepository { Future get(String id) async { final user = await _db.managers.authUserEntity.filter((user) => user.id.equals(id)).getSingleOrNull(); - if (user == null) return null; + if (user == null) { + return null; + } final query = _db.userMetadataEntity.select()..where((e) => e.userId.equals(id)); final metadata = await query.map((row) => row.toDto()).get(); diff --git a/mobile/lib/infrastructure/repositories/user_api.repository.dart b/mobile/lib/infrastructure/repositories/user_api.repository.dart index d21a1b71a6..aa645f2a4a 100644 --- a/mobile/lib/infrastructure/repositories/user_api.repository.dart +++ b/mobile/lib/infrastructure/repositories/user_api.repository.dart @@ -12,7 +12,9 @@ class UserApiRepository extends ApiRepository { Future getMyUser() async { final (adminDto, preferenceDto) = await (_api.getMyUser(), _api.getMyPreferences()).wait; - if (adminDto == null) return null; + if (adminDto == null) { + return null; + } return UserConverter.fromAdminDto(adminDto, preferenceDto); } diff --git a/mobile/lib/infrastructure/utils/exif.converter.dart b/mobile/lib/infrastructure/utils/exif.converter.dart index 50639e8e42..9f9b6f9324 100644 --- a/mobile/lib/infrastructure/utils/exif.converter.dart +++ b/mobile/lib/infrastructure/utils/exif.converter.dart @@ -5,24 +5,24 @@ import 'package:openapi/api.dart'; abstract final class ExifDtoConverter { static ExifInfo fromDto(ExifResponseDto dto) { return ExifInfo( - fileSize: dto.fileSizeInByte, - description: dto.description, - orientation: dto.orientation, - timeZone: dto.timeZone, - dateTimeOriginal: dto.dateTimeOriginal, - isFlipped: isOrientationFlipped(dto.orientation), - latitude: dto.latitude?.toDouble(), - longitude: dto.longitude?.toDouble(), - city: dto.city, - state: dto.state, - country: dto.country, - make: dto.make, - model: dto.model, - lens: dto.lensModel, - f: dto.fNumber?.toDouble(), - mm: dto.focalLength?.toDouble(), - iso: dto.iso?.toInt(), - exposureSeconds: exposureTimeToSeconds(dto.exposureTime), + fileSize: dto.fileSizeInByte.orElse(null), + description: dto.description.orElse(null), + orientation: dto.orientation.orElse(null), + timeZone: dto.timeZone.orElse(null), + dateTimeOriginal: dto.dateTimeOriginal.orElse(null), + isFlipped: isOrientationFlipped(dto.orientation.orElse(null)), + latitude: dto.latitude.orElse(null)?.toDouble(), + longitude: dto.longitude.orElse(null)?.toDouble(), + city: dto.city.orElse(null), + state: dto.state.orElse(null), + country: dto.country.orElse(null), + make: dto.make.orElse(null), + model: dto.model.orElse(null), + lens: dto.lensModel.orElse(null), + f: dto.fNumber.orElse(null)?.toDouble(), + mm: dto.focalLength.orElse(null)?.toDouble(), + iso: dto.iso.orElse(null)?.toInt(), + exposureSeconds: exposureTimeToSeconds(dto.exposureTime.orElse(null)), ); } diff --git a/mobile/lib/infrastructure/utils/user.converter.dart b/mobile/lib/infrastructure/utils/user.converter.dart index 826649b247..7e8a2c6fcc 100644 --- a/mobile/lib/infrastructure/utils/user.converter.dart +++ b/mobile/lib/infrastructure/utils/user.converter.dart @@ -40,7 +40,7 @@ abstract final class UserConverter { updatedAt: DateTime.now(), avatarColor: dto.avatarColor.toAvatarColor(), memoryEnabled: false, - inTimeline: dto.inTimeline ?? false, + inTimeline: dto.inTimeline.orElse(null) ?? false, isPartnerSharedBy: false, isPartnerSharedWith: false, profileChangedAt: dto.profileChangedAt, diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart index 4a284b9bda..75f1c2221a 100644 --- a/mobile/lib/main.dart +++ b/mobile/lib/main.dart @@ -10,6 +10,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_displaymode/flutter_displaymode.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/constants.dart'; import 'package:immich_mobile/constants/locales.dart'; @@ -23,7 +24,9 @@ import 'package:immich_mobile/pages/common/splash_screen.page.dart'; import 'package:immich_mobile/platform/background_worker_lock_api.g.dart'; import 'package:immich_mobile/providers/app_life_cycle.provider.dart'; import 'package:immich_mobile/providers/asset_viewer/share_intent_upload.provider.dart'; +import 'package:immich_mobile/providers/view_intent/view_intent_handler.provider.dart'; import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; import 'package:immich_mobile/providers/infrastructure/platform.provider.dart'; import 'package:immich_mobile/providers/locale_provider.dart'; import 'package:immich_mobile/providers/routes.provider.dart'; @@ -53,7 +56,7 @@ void main() async { await initApp(); // Warm-up isolate pool for worker manager await workerManagerPatch.init(dynamicSpawning: true, isolatesCount: max(Platform.numberOfProcessors - 1, 5)); - await migrateDatabaseIfNeeded(); + await migrateDatabaseIfNeeded(drift); runApp(ProviderScope(overrides: [driftProvider.overrideWith(driftOverride(drift))], child: const MainWidget())); } catch (error, stack) { @@ -126,6 +129,7 @@ class ImmichAppState extends ConsumerState with WidgetsBindingObserve case AppLifecycleState.resumed: dPrint(() => "[APP STATE] resumed"); ref.read(appStateProvider.notifier).handleAppResume(); + unawaited(ref.read(viewIntentHandlerProvider).onAppResumed()); break; case AppLifecycleState.inactive: dPrint(() => "[APP STATE] inactive"); @@ -162,6 +166,13 @@ class ImmichAppState extends ConsumerState with WidgetsBindingObserve } } SystemChrome.setSystemUIOverlayStyle(overlayStyle); + + await FlutterLocalNotificationsPlugin().initialize( + const InitializationSettings( + android: AndroidInitializationSettings('@drawable/notification_icon'), + iOS: DarwinInitializationSettings(), + ), + ); } Future _deepLinkBuilder(PlatformDeepLink deepLink) async { @@ -170,19 +181,32 @@ class ImmichAppState extends ConsumerState with WidgetsBindingObserve final isColdStart = currentRouteName == null || currentRouteName == SplashScreenRoute.name; + PageRouteInfo? route; if (deepLink.uri.scheme == "immich") { - final proposedRoute = await deepLinkHandler.handleScheme(deepLink, ref, isColdStart); - - return proposedRoute; + route = await deepLinkHandler.handleScheme(deepLink, ref); + } else if (deepLink.uri.host == "my.immich.app") { + route = await deepLinkHandler.handleMyImmichApp(deepLink, ref); + } else { + return DeepLink.path(deepLink.path); } - if (deepLink.uri.host == "my.immich.app") { - final proposedRoute = await deepLinkHandler.handleMyImmichApp(deepLink, ref, isColdStart); - - return proposedRoute; + if (route == null) { + return isColdStart ? DeepLink.defaultPath : DeepLink.none; } - return DeepLink.path(deepLink.path); + // We need to replace the route if the destination is the current route + if (!isColdStart) { + unawaited( + ref.read(appRouterProvider).pushAndPopUntil(route, predicate: (r) => r.settings.name != route!.routeName), + ); + return DeepLink.none; + } + + return DeepLink([ + // we need something to segue back to if the app was cold started + if (isColdStart) const TabShellRoute(children: [MainTimelineRoute()]), + route, + ]); } @override @@ -211,6 +235,7 @@ class ImmichAppState extends ConsumerState with WidgetsBindingObserve } }); + ref.read(viewIntentHandlerProvider).init(); ref.read(shareIntentUploadProvider.notifier).init(); } @@ -241,7 +266,7 @@ class ImmichAppState extends ConsumerState with WidgetsBindingObserve localizationsDelegates: context.localizationDelegates, supportedLocales: context.supportedLocales, locale: context.locale, - themeMode: ref.watch(immichThemeModeProvider), + themeMode: ref.watch(appConfigProvider.select((config) => config.theme.mode)), darkTheme: getThemeData(colorScheme: immichTheme.dark, locale: context.locale), theme: getThemeData(colorScheme: immichTheme.light, locale: context.locale), builder: (context, child) => ImmichTranslationProvider( diff --git a/mobile/lib/models/activities/activity.model.dart b/mobile/lib/models/activities/activity.model.dart index 38c2bef77a..d3f99aea64 100644 --- a/mobile/lib/models/activities/activity.model.dart +++ b/mobile/lib/models/activities/activity.model.dart @@ -44,7 +44,9 @@ class Activity { @override bool operator ==(covariant Activity other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } return other.id == id && other.assetId == assetId && diff --git a/mobile/lib/models/auth/auth_state.model.dart b/mobile/lib/models/auth/auth_state.model.dart index 0d8357d66d..c8a8018929 100644 --- a/mobile/lib/models/auth/auth_state.model.dart +++ b/mobile/lib/models/auth/auth_state.model.dart @@ -44,7 +44,9 @@ class AuthState { @override bool operator ==(Object other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } return other is AuthState && other.deviceId == deviceId && diff --git a/mobile/lib/models/auth/auxilary_endpoint.model.dart b/mobile/lib/models/auth/auxilary_endpoint.model.dart index c7f472e111..cd11918bed 100644 --- a/mobile/lib/models/auth/auxilary_endpoint.model.dart +++ b/mobile/lib/models/auth/auxilary_endpoint.model.dart @@ -16,7 +16,9 @@ class AuxilaryEndpoint { @override bool operator ==(covariant AuxilaryEndpoint other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } return other.url == url && other.status == status; } @@ -53,7 +55,9 @@ class AuxCheckStatus { @override bool operator ==(covariant AuxCheckStatus other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } return other.name == name; } diff --git a/mobile/lib/models/auth/biometric_status.model.dart b/mobile/lib/models/auth/biometric_status.model.dart index ad2b06be04..c5c417d06d 100644 --- a/mobile/lib/models/auth/biometric_status.model.dart +++ b/mobile/lib/models/auth/biometric_status.model.dart @@ -19,7 +19,9 @@ class BiometricStatus { @override bool operator ==(covariant BiometricStatus other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } final listEquals = const DeepCollectionEquality().equals; return listEquals(other.availableBiometrics, availableBiometrics) && other.canAuthenticate == canAuthenticate; diff --git a/mobile/lib/models/cast/cast_manager_state.dart b/mobile/lib/models/cast/cast_manager_state.dart index c948921792..9727bc7ed8 100644 --- a/mobile/lib/models/cast/cast_manager_state.dart +++ b/mobile/lib/models/cast/cast_manager_state.dart @@ -67,7 +67,9 @@ class CastManagerState { @override bool operator ==(Object other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } return other is CastManagerState && other.isCasting == isCasting && diff --git a/mobile/lib/models/download/download_state.model.dart b/mobile/lib/models/download/download_state.model.dart index 82d4e31253..bed92d98b6 100644 --- a/mobile/lib/models/download/download_state.model.dart +++ b/mobile/lib/models/download/download_state.model.dart @@ -41,7 +41,9 @@ class DownloadInfo { @override bool operator ==(covariant DownloadInfo other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } return other.fileName == fileName && other.progress == progress && other.status == status; } @@ -71,7 +73,9 @@ class DownloadState { @override bool operator ==(covariant DownloadState other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } final mapEquals = const DeepCollectionEquality().equals; return other.downloadStatus == downloadStatus && diff --git a/mobile/lib/models/download/livephotos_medatada.model.dart b/mobile/lib/models/download/livephotos_medatada.model.dart index f77a1514ac..228ad707da 100644 --- a/mobile/lib/models/download/livephotos_medatada.model.dart +++ b/mobile/lib/models/download/livephotos_medatada.model.dart @@ -32,7 +32,9 @@ class LivePhotosMetadata { @override bool operator ==(covariant LivePhotosMetadata other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } return other.part == part && other.id == id; } diff --git a/mobile/lib/models/map/map_marker.model.dart b/mobile/lib/models/map/map_marker.model.dart index 0f425306ff..d730f9bd6d 100644 --- a/mobile/lib/models/map/map_marker.model.dart +++ b/mobile/lib/models/map/map_marker.model.dart @@ -17,7 +17,9 @@ class MapMarker { @override bool operator ==(covariant MapMarker other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } return other.latLng == latLng && other.assetRemoteId == assetRemoteId; } diff --git a/mobile/lib/models/map/map_state.model.dart b/mobile/lib/models/map/map_state.model.dart index 78747e770d..a4863aa465 100644 --- a/mobile/lib/models/map/map_state.model.dart +++ b/mobile/lib/models/map/map_state.model.dart @@ -51,7 +51,9 @@ class MapState { @override bool operator ==(covariant MapState other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } return other.themeMode == themeMode && other.showFavoriteOnly == showFavoriteOnly && diff --git a/mobile/lib/models/search/search_curated_content.model.dart b/mobile/lib/models/search/search_curated_content.model.dart index 6e4a083876..58c4c73264 100644 --- a/mobile/lib/models/search/search_curated_content.model.dart +++ b/mobile/lib/models/search/search_curated_content.model.dart @@ -42,7 +42,9 @@ class SearchCuratedContent { @override bool operator ==(covariant SearchCuratedContent other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } return other.label == label && other.subtitle == subtitle && other.id == id; } diff --git a/mobile/lib/models/search/search_filter.model.dart b/mobile/lib/models/search/search_filter.model.dart index 16f3be4655..cf1a1dcdaf 100644 --- a/mobile/lib/models/search/search_filter.model.dart +++ b/mobile/lib/models/search/search_filter.model.dart @@ -36,7 +36,9 @@ class SearchLocationFilter { @override bool operator ==(covariant SearchLocationFilter other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } return other.country == country && other.state == state && other.city == city; } @@ -75,7 +77,9 @@ class SearchCameraFilter { @override bool operator ==(covariant SearchCameraFilter other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } return other.make == make && other.model == model; } @@ -117,7 +121,9 @@ class SearchDateFilter { @override bool operator ==(covariant SearchDateFilter other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } return other.takenBefore == takenBefore && other.takenAfter == takenAfter; } @@ -152,7 +158,9 @@ class SearchRatingFilter { @override bool operator ==(covariant SearchRatingFilter other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } return other.rating == rating; } @@ -198,7 +206,9 @@ class SearchDisplayFilters { @override bool operator ==(covariant SearchDisplayFilters other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } return other.isNotInAlbum == isNotInAlbum && other.isArchive == isArchive && other.isFavorite == isFavorite; } @@ -305,7 +315,9 @@ class SearchFilter { @override bool operator ==(covariant SearchFilter other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } return other.context == context && other.filename == filename && diff --git a/mobile/lib/models/server_info/server_config.model.dart b/mobile/lib/models/server_info/server_config.model.dart index 37b98afadb..15bbe41485 100644 --- a/mobile/lib/models/server_info/server_config.model.dart +++ b/mobile/lib/models/server_info/server_config.model.dart @@ -38,7 +38,9 @@ class ServerConfig { @override bool operator ==(covariant ServerConfig other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } return other.trashDays == trashDays && other.oauthButtonText == oauthButtonText && diff --git a/mobile/lib/models/server_info/server_disk_info.model.dart b/mobile/lib/models/server_info/server_disk_info.model.dart index 01042b9f6d..16e58b331d 100644 --- a/mobile/lib/models/server_info/server_disk_info.model.dart +++ b/mobile/lib/models/server_info/server_disk_info.model.dart @@ -35,7 +35,9 @@ class ServerDiskInfo { @override bool operator ==(Object other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } return other is ServerDiskInfo && other.diskAvailable == diskAvailable && diff --git a/mobile/lib/models/server_info/server_features.model.dart b/mobile/lib/models/server_info/server_features.model.dart index 78a80c9013..c288c1bfbf 100644 --- a/mobile/lib/models/server_info/server_features.model.dart +++ b/mobile/lib/models/server_info/server_features.model.dart @@ -50,7 +50,9 @@ class ServerFeatures { @override bool operator ==(covariant ServerFeatures other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } return other.trash == trash && other.map == map && diff --git a/mobile/lib/models/server_info/server_info.model.dart b/mobile/lib/models/server_info/server_info.model.dart index a039bb70eb..33d6393e15 100644 --- a/mobile/lib/models/server_info/server_info.model.dart +++ b/mobile/lib/models/server_info/server_info.model.dart @@ -60,7 +60,9 @@ class ServerInfo { @override bool operator ==(Object other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } return other is ServerInfo && other.serverVersion == serverVersion && diff --git a/mobile/lib/models/server_info/server_version.model.dart b/mobile/lib/models/server_info/server_version.model.dart index c8bf73db81..40f35a3cd0 100644 --- a/mobile/lib/models/server_info/server_version.model.dart +++ b/mobile/lib/models/server_info/server_version.model.dart @@ -2,16 +2,12 @@ import 'package:immich_mobile/utils/semver.dart'; import 'package:openapi/api.dart'; class ServerVersion extends SemVer { - const ServerVersion({required super.major, required super.minor, required super.patch}); + const ServerVersion({required super.major, required super.minor, required super.patch, super.prerelease}); - @override - String toString() { - return 'ServerVersion(major: $major, minor: $minor, patch: $patch)'; - } + ServerVersion.fromDto(ServerVersionResponseDto dto) + : super(major: dto.major, minor: dto.minor, patch: dto.patch_, prerelease: dto.prerelease); - ServerVersion.fromDto(ServerVersionResponseDto dto) : super(major: dto.major, minor: dto.minor, patch: dto.patch_); - - bool isAtLeast({int major = 0, int minor = 0, int patch = 0}) { - return this >= SemVer(major: major, minor: minor, patch: patch); + bool isAtLeast({int major = 0, int minor = 0, int patch = 0, int? prerelease}) { + return this >= SemVer(major: major, minor: minor, patch: patch, prerelease: prerelease); } } diff --git a/mobile/lib/models/shared_link/shared_link.model.dart b/mobile/lib/models/shared_link/shared_link.model.dart index 4315cf616a..e7b65a96ef 100644 --- a/mobile/lib/models/shared_link/shared_link.model.dart +++ b/mobile/lib/models/shared_link/shared_link.model.dart @@ -73,10 +73,10 @@ class SharedLink { slug = dto.slug, type = dto.type == SharedLinkType.ALBUM ? SharedLinkSource.album : SharedLinkSource.individual, title = dto.type == SharedLinkType.ALBUM - ? dto.album?.albumName.toUpperCase() ?? "UNKNOWN SHARE" + ? dto.album.orElse(null)?.albumName.toUpperCase() ?? "UNKNOWN SHARE" : "INDIVIDUAL SHARE", thumbAssetId = dto.type == SharedLinkType.ALBUM - ? dto.album?.albumThumbnailAssetId + ? dto.album.orElse(null)?.albumThumbnailAssetId : dto.assets.isNotEmpty ? dto.assets[0].id : null; diff --git a/mobile/lib/models/upload/share_intent_attachment.model.dart b/mobile/lib/models/upload/share_intent_attachment.model.dart index e5388fce2c..0f9c3e4c29 100644 --- a/mobile/lib/models/upload/share_intent_attachment.model.dart +++ b/mobile/lib/models/upload/share_intent_attachment.model.dart @@ -88,7 +88,9 @@ class ShareIntentAttachment { @override bool operator ==(covariant ShareIntentAttachment other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } return other.path == path && other.type == type; } diff --git a/mobile/lib/models/view_intent/view_intent_payload.extension.dart b/mobile/lib/models/view_intent/view_intent_payload.extension.dart new file mode 100644 index 0000000000..ca66e6a163 --- /dev/null +++ b/mobile/lib/models/view_intent/view_intent_payload.extension.dart @@ -0,0 +1,35 @@ +import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; +import 'package:immich_mobile/platform/view_intent_api.g.dart'; +import 'package:path/path.dart'; + +extension ViewIntentPayloadX on ViewIntentPayload { + String get fileName { + final resolvedPath = path; + if (resolvedPath != null && resolvedPath.isNotEmpty) { + return basename(resolvedPath); + } + return localAssetId ?? 'view_intent_asset'; + } + + bool get isImage => mimeType.toLowerCase().startsWith('image/'); + + bool get isVideo => mimeType.toLowerCase().startsWith('video/'); + + AssetPlaybackStyle get playbackStyle { + if (isVideo) { + return AssetPlaybackStyle.video; + } + + final normalizedMimeType = mimeType.toLowerCase(); + if (normalizedMimeType == 'image/gif' || normalizedMimeType == 'image/webp') { + return AssetPlaybackStyle.imageAnimated; + } + + final normalizedPath = path?.toLowerCase(); + if (normalizedPath != null && (normalizedPath.endsWith('.gif') || normalizedPath.endsWith('.webp'))) { + return AssetPlaybackStyle.imageAnimated; + } + + return AssetPlaybackStyle.image; + } +} diff --git a/mobile/lib/pages/backup/drift_backup.page.dart b/mobile/lib/pages/backup/drift_backup.page.dart index 6bdb8dd552..2e18c3edc6 100644 --- a/mobile/lib/pages/backup/drift_backup.page.dart +++ b/mobile/lib/pages/backup/drift_backup.page.dart @@ -418,7 +418,9 @@ class _PreparingStatusState extends ConsumerState { } void _startPollingIfNeeded() { - if (_pollingTimer != null) return; + if (_pollingTimer != null) { + return; + } _pollingTimer = Timer.periodic(const Duration(seconds: 3), (timer) async { final currentUser = ref.read(currentUserProvider); diff --git a/mobile/lib/pages/backup/drift_backup_album_selection.page.dart b/mobile/lib/pages/backup/drift_backup_album_selection.page.dart index 1732385675..f999e7671f 100644 --- a/mobile/lib/pages/backup/drift_backup_album_selection.page.dart +++ b/mobile/lib/pages/backup/drift_backup_album_selection.page.dart @@ -8,13 +8,13 @@ import 'package:immich_mobile/domain/models/album/local_album.model.dart'; import 'package:immich_mobile/domain/services/sync_linked_album.service.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; -import 'package:immich_mobile/providers/app_settings.provider.dart'; +import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart'; import 'package:immich_mobile/providers/background_sync.provider.dart'; import 'package:immich_mobile/providers/backup/backup_album.provider.dart'; import 'package:immich_mobile/providers/backup/drift_backup.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; import 'package:immich_mobile/providers/infrastructure/platform.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; -import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/widgets/backup/drift_album_info_list_tile.dart'; import 'package:immich_mobile/widgets/common/search_field.dart'; import 'package:logging/logging.dart'; @@ -43,7 +43,7 @@ class _DriftBackupAlbumSelectionPageState extends ConsumerState p.totalCount)); @@ -55,7 +55,7 @@ class _DriftBackupAlbumSelectionPageState extends ConsumerState a.backupSelection == BackupSelection.selected) @@ -83,7 +83,9 @@ class _DriftBackupAlbumSelectionPageState extends ConsumerState p.totalCount)); final totalChanged = currentTotalAssetCount != _initialTotalAssetCount; diff --git a/mobile/lib/pages/backup/drift_backup_options.page.dart b/mobile/lib/pages/backup/drift_backup_options.page.dart index 79891d7002..42643a0496 100644 --- a/mobile/lib/pages/backup/drift_backup_options.page.dart +++ b/mobile/lib/pages/backup/drift_backup_options.page.dart @@ -3,14 +3,12 @@ import 'dart:async'; import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/store.model.dart'; -import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; -import 'package:immich_mobile/providers/app_settings.provider.dart'; +import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart'; import 'package:immich_mobile/providers/background_sync.provider.dart'; import 'package:immich_mobile/providers/backup/drift_backup.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; -import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/widgets/settings/backup_settings/drift_backup_settings.dart'; import 'package:logging/logging.dart'; @@ -21,18 +19,20 @@ class DriftBackupOptionsPage extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { bool hasPopped = false; - final previousWifiReqForVideos = Store.tryGet(StoreKey.useWifiForUploadVideos) ?? false; - final previousWifiReqForPhotos = Store.tryGet(StoreKey.useWifiForUploadPhotos) ?? false; + final previousBackup = ref.read(appConfigProvider).backup; + final previousCellularForVideos = previousBackup.useCellularForVideos; + final previousCellularForPhotos = previousBackup.useCellularForPhotos; return PopScope( onPopInvokedWithResult: (didPop, result) async { // There is an issue with Flutter where the pop event // can be triggered multiple times, so we guard it with _hasPopped - final currentWifiReqForVideos = Store.tryGet(StoreKey.useWifiForUploadVideos) ?? false; - final currentWifiReqForPhotos = Store.tryGet(StoreKey.useWifiForUploadPhotos) ?? false; + final currentBackup = ref.read(appConfigProvider).backup; + final currentCellularForVideos = currentBackup.useCellularForVideos; + final currentCellularForPhotos = currentBackup.useCellularForPhotos; - if (currentWifiReqForVideos == previousWifiReqForVideos && - currentWifiReqForPhotos == previousWifiReqForPhotos) { + if (currentCellularForVideos == previousCellularForVideos && + currentCellularForPhotos == previousCellularForPhotos) { return; } @@ -45,7 +45,7 @@ class DriftBackupOptionsPage extends ConsumerWidget { } await ref.read(driftBackupProvider.notifier).getBackupStatus(currentUser.id); - final isBackupEnabled = ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.enableBackup); + final isBackupEnabled = SettingsRepository.instance.appConfig.backup.enabled; if (!isBackupEnabled) { return; } diff --git a/mobile/lib/pages/backup/drift_upload_detail.page.dart b/mobile/lib/pages/backup/drift_upload_detail.page.dart index 71249d1c4b..978fcf7c57 100644 --- a/mobile/lib/pages/backup/drift_upload_detail.page.dart +++ b/mobile/lib/pages/backup/drift_upload_detail.page.dart @@ -40,7 +40,9 @@ class _DriftUploadDetailPageState extends ConsumerState { } for (final item in uploadingItems) { - if (_taskSlotAssignments.containsKey(item.taskId)) continue; + if (_taskSlotAssignments.containsKey(item.taskId)) { + continue; + } for (int i = 0; i < _maxSlots; i++) { if (slots[i] == null) { diff --git a/mobile/lib/pages/common/headers_settings.page.dart b/mobile/lib/pages/common/headers_settings.page.dart index 6eba49442f..9a6b602b04 100644 --- a/mobile/lib/pages/common/headers_settings.page.dart +++ b/mobile/lib/pages/common/headers_settings.page.dart @@ -1,14 +1,11 @@ -import 'dart:convert'; - import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart' hide Store; +import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/store.model.dart'; -import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/generated/translations.g.dart'; import 'package:immich_mobile/providers/api.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; class SettingsHeader { String key = ""; @@ -24,17 +21,14 @@ class HeaderSettingsPage extends HookConsumerWidget { final headers = useState>([]); final setInitialHeaders = useState(false); - var headersStr = Store.get(StoreKey.customHeaders, ""); + final storedHeaders = ref.read(appConfigProvider).network.customHeaders; if (!setInitialHeaders.value) { - if (headersStr.isNotEmpty) { - var customHeaders = jsonDecode(headersStr) as Map; - customHeaders.forEach((k, v) { - final header = SettingsHeader(); - header.key = k; - header.value = v; - headers.value.add(header); - }); - } + storedHeaders.forEach((k, v) { + final header = SettingsHeader(); + header.key = k; + header.value = v; + headers.value.add(header); + }); // add first one to help the user if (headers.value.isEmpty) { @@ -88,17 +82,18 @@ class HeaderSettingsPage extends HookConsumerWidget { } saveHeaders(WidgetRef ref, List headers) async { - final headersMap = {}; - for (var header in headers) { + final headersMap = {}; + for (final header in headers) { final key = header.key.trim(); final value = header.value.trim(); - if (key.isEmpty || value.isEmpty) continue; + if (key.isEmpty || value.isEmpty) { + continue; + } headersMap[key] = value; } - var encoded = jsonEncode(headersMap); - await Store.put(StoreKey.customHeaders, encoded); + await ref.read(settingsProvider).write(.networkCustomHeaders, headersMap); await ref.read(apiServiceProvider).updateHeaders(); } } diff --git a/mobile/lib/pages/common/splash_screen.page.dart b/mobile/lib/pages/common/splash_screen.page.dart index 725f7f9e85..de6fda5773 100644 --- a/mobile/lib/pages/common/splash_screen.page.dart +++ b/mobile/lib/pages/common/splash_screen.page.dart @@ -6,16 +6,18 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/constants/colors.dart'; import 'package:immich_mobile/constants/locales.dart'; +import 'package:immich_mobile/domain/models/config/app_config.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/generated/codegen_loader.g.dart'; import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart'; import 'package:immich_mobile/providers/auth.provider.dart'; import 'package:immich_mobile/providers/background_sync.provider.dart'; import 'package:immich_mobile/providers/backup/drift_backup.provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; +import 'package:immich_mobile/providers/view_intent/view_intent_handler.provider.dart'; import 'package:immich_mobile/providers/websocket.provider.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/theme/color_scheme.dart'; @@ -35,7 +37,7 @@ class BootstrapErrorWidget extends StatelessWidget { @override Widget build(BuildContext _) { - final immichTheme = defaultColorPreset.themeOfPreset; + final immichTheme = defaultConfig.theme.primaryColor.themeOfPreset; return EasyLocalization( supportedLocales: locales.values.toList(), @@ -313,6 +315,7 @@ class SplashScreenPageState extends ConsumerState { final wsProvider = ref.read(websocketProvider.notifier); final backgroundManager = ref.read(backgroundSyncProvider); final backupProvider = ref.read(driftBackupProvider.notifier); + final viewIntentHandler = ref.read(viewIntentHandlerProvider); unawaited( ref.read(authProvider.notifier).saveAuthInfo(accessToken: accessToken).then( @@ -327,6 +330,8 @@ class SplashScreenPageState extends ConsumerState { backgroundManager.syncRemote().then((success) => syncSuccess = success), ]); + await viewIntentHandler.flushDeferredViewIntent(); + if (syncSuccess) { await Future.wait([ backgroundManager.hashAssets().then((_) { @@ -340,7 +345,7 @@ class SplashScreenPageState extends ConsumerState { await backgroundManager.hashAssets(); } - if (Store.get(StoreKey.syncAlbums, false)) { + if (SettingsRepository.instance.appConfig.backup.syncAlbums) { await backgroundManager.syncLinkedAlbum(); } } catch (e) { @@ -369,7 +374,7 @@ class SplashScreenPageState extends ConsumerState { } Future _resumeBackup(DriftBackupNotifier notifier) async { - final isEnableBackup = Store.get(StoreKey.enableBackup, false); + final isEnableBackup = SettingsRepository.instance.appConfig.backup.enabled; if (isEnableBackup) { final currentUser = Store.tryGet(StoreKey.currentUser); diff --git a/mobile/lib/pages/library/folder/folder.page.dart b/mobile/lib/pages/library/folder/folder.page.dart index 9de230d550..e2766df547 100644 --- a/mobile/lib/pages/library/folder/folder.page.dart +++ b/mobile/lib/pages/library/folder/folder.page.dart @@ -31,7 +31,9 @@ RecursiveFolder? _findFolderInStructure(RootFolder rootFolder, RecursiveFolder t if (folder.subfolders.isNotEmpty) { final found = _findFolderInStructure(folder, targetFolder); - if (found != null) return found; + if (found != null) { + return found; + } } } return null; @@ -113,7 +115,9 @@ class FolderContent extends HookConsumerWidget { // Initial asset fetch useEffect(() { - if (folder == null) return; + if (folder == null) { + return; + } ref.read(folderRenderListProvider(folder!).notifier).fetchAssets(sortOrder); return null; }, [folder]); diff --git a/mobile/lib/pages/library/shared_link/shared_link.page.dart b/mobile/lib/pages/library/shared_link/shared_link.page.dart index 66a77fb761..a4f52ebd4c 100644 --- a/mobile/lib/pages/library/shared_link/shared_link.page.dart +++ b/mobile/lib/pages/library/shared_link/shared_link.page.dart @@ -4,7 +4,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; -import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/models/shared_link/shared_link.model.dart'; import 'package:immich_mobile/providers/shared_link.provider.dart'; import 'package:immich_mobile/widgets/shared_link/shared_link_item.dart'; @@ -20,77 +19,49 @@ class SharedLinkPage extends HookConsumerWidget { useEffect(() { ref.read(sharedLinksStateProvider.notifier).fetchLinks(); return () { - if (!context.mounted) return; + if (!context.mounted) { + return; + } ref.invalidate(sharedLinksStateProvider); }; }, []); Widget buildNoShares() { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.only(left: 16.0, top: 16.0), - child: const Text( - "shared_link_manage_links", - style: TextStyle(fontSize: 14, color: Colors.grey, fontWeight: FontWeight.bold), - ).tr(), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 10), - child: const Text("you_dont_have_any_shared_links", style: TextStyle(fontSize: 14)).tr(), - ), - ), - Expanded( - child: Center( - child: Icon(Icons.link_off, size: 100, color: context.themeData.iconTheme.color?.withValues(alpha: 0.5)), - ), - ), - ], + return Center( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.link_off, size: 100, color: Theme.of(context).colorScheme.onSurface.withAlpha(128)), + const SizedBox(height: 20), + const Text("you_dont_have_any_shared_links", style: TextStyle(fontSize: 14)).tr(), + ], + ), ); } Widget buildSharesList(List links) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.only(left: 16.0, top: 16.0, bottom: 30.0), - child: Text( - "shared_link_manage_links", - style: context.textTheme.labelLarge?.copyWith(color: context.textTheme.labelLarge?.color?.withAlpha(200)), - ).tr(), - ), - Expanded( - child: LayoutBuilder( - builder: (context, constraints) { - if (constraints.maxWidth > 600) { - // Two column - return GridView.builder( - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 2, - mainAxisExtent: 180, - ), - itemCount: links.length, - itemBuilder: (context, index) { - return SharedLinkItem(links.elementAt(index)); - }, - ); - } - - // Single column - return ListView.builder( - itemCount: links.length, - itemBuilder: (context, index) { - return SharedLinkItem(links.elementAt(index)); - }, - ); - }, - ), - ), - ], + return LayoutBuilder( + builder: (context, constraints) => constraints.maxWidth > 600 + ? GridView.builder( + key: const PageStorageKey('shared-links-grid'), + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + mainAxisExtent: 180, + crossAxisSpacing: 12, + mainAxisSpacing: 12, + ), + padding: const EdgeInsets.all(12), + itemCount: links.length, + itemBuilder: (context, index) => SharedLinkItem(links[index]), + ) + : ListView.separated( + key: const PageStorageKey('shared-links-list'), + padding: const EdgeInsets.symmetric(vertical: 8), + itemCount: links.length, + itemBuilder: (context, index) => SharedLinkItem(links[index]), + separatorBuilder: (context, index) => const Divider(height: 1), + ), ); } diff --git a/mobile/lib/pages/library/shared_link/shared_link_edit.page.dart b/mobile/lib/pages/library/shared_link/shared_link_edit.page.dart index 47a3dd853d..65973918e4 100644 --- a/mobile/lib/pages/library/shared_link/shared_link_edit.page.dart +++ b/mobile/lib/pages/library/shared_link/shared_link_edit.page.dart @@ -6,15 +6,21 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/generated/translations.g.dart'; import 'package:immich_mobile/models/shared_link/shared_link.model.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; import 'package:immich_mobile/providers/shared_link.provider.dart'; import 'package:immich_mobile/services/shared_link.service.dart'; +import 'package:openapi/api.dart'; import 'package:immich_mobile/utils/url_helper.dart'; +import 'package:immich_mobile/widgets/common/confirm_dialog.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; +import 'package:share_plus/share_plus.dart'; @RoutePage() class SharedLinkEditPage extends HookConsumerWidget { + static const int maxFutureDate = 365 * 2; + final SharedLink? existingLink; final List? assetsList; final String? albumId; @@ -23,71 +29,82 @@ class SharedLinkEditPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - const padding = 20.0; final themeData = context.themeData; final colorScheme = context.colorScheme; + final externalDomain = ref.watch(serverInfoProvider.select((s) => s.serverConfig.externalDomain)); + final displayServerUrl = externalDomain.isNotEmpty ? externalDomain : getServerUrl(); + final expiryPresets = <(Duration, String)>[ + (Duration.zero, context.t.never), + (const Duration(minutes: 30), context.t.shared_link_edit_expire_after_option_minutes(count: 30)), + (const Duration(hours: 1), context.t.shared_link_edit_expire_after_option_hour), + (const Duration(hours: 6), context.t.shared_link_edit_expire_after_option_hours(count: 6)), + (const Duration(days: 1), context.t.shared_link_edit_expire_after_option_day), + (const Duration(days: 7), context.t.shared_link_edit_expire_after_option_days(count: 7)), + (const Duration(days: 30), context.t.shared_link_edit_expire_after_option_days(count: 30)), + (const Duration(days: 90), context.t.shared_link_edit_expire_after_option_months(count: 3)), + (const Duration(days: 365), context.t.shared_link_edit_expire_after_option_year(count: 1)), + ]; final descriptionController = useTextEditingController(text: existingLink?.description ?? ""); final descriptionFocusNode = useFocusNode(); final passwordController = useTextEditingController(text: existingLink?.password ?? ""); final slugController = useTextEditingController(text: existingLink?.slug ?? ""); final slugFocusNode = useFocusNode(); + useListenable(slugController); final showMetadata = useState(existingLink?.showMetadata ?? true); final allowDownload = useState(existingLink?.allowDownload ?? true); final allowUpload = useState(existingLink?.allowUpload ?? false); - final editExpiry = useState(false); - final expiryAfter = useState(0); + final expiryAfter = useState(existingLink?.expiresAt?.toLocal()); + final selectedPresetIndex = useState(existingLink?.expiresAt == null ? 0 : null); final newShareLink = useState(""); + Widget buildSharedLinkRow({required String leading, required String content}) { + return Row( + children: [ + Expanded( + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Text( + content, + style: TextStyle(color: colorScheme.primary, fontWeight: FontWeight.bold), + ), + ), + ), + const SizedBox(width: 8), + Text(leading, style: const TextStyle(fontWeight: FontWeight.bold)), + ], + ); + } + Widget buildLinkTitle() { if (existingLink != null) { if (existingLink!.type == SharedLinkSource.album) { - return Row( - children: [ - const Text('public_album', style: TextStyle(fontWeight: FontWeight.bold)).tr(), - const Text(" | ", style: TextStyle(fontWeight: FontWeight.bold)), - Text( - existingLink!.title, - style: TextStyle(color: colorScheme.primary, fontWeight: FontWeight.bold), - ), - ], - ); + return buildSharedLinkRow(leading: context.t.public_album, content: existingLink!.title); } if (existingLink!.type == SharedLinkSource.individual) { - return Row( - children: [ - const Text('shared_link_individual_shared', style: TextStyle(fontWeight: FontWeight.bold)).tr(), - const Text(" | ", style: TextStyle(fontWeight: FontWeight.bold)), - Expanded( - child: Text( - existingLink!.description ?? "--", - style: TextStyle(color: colorScheme.primary, fontWeight: FontWeight.bold), - overflow: TextOverflow.ellipsis, - ), - ), - ], + return buildSharedLinkRow( + leading: context.t.shared_link_individual_shared, + content: existingLink!.description ?? "--", ); } } - return const Text("create_link_to_share_description", style: TextStyle(fontWeight: FontWeight.bold)).tr(); + return Text(context.t.create_link_to_share_description, style: const TextStyle(fontWeight: FontWeight.bold)); } Widget buildDescriptionField() { return TextField( controller: descriptionController, - enabled: newShareLink.value.isEmpty, focusNode: descriptionFocusNode, textInputAction: TextInputAction.done, autofocus: false, decoration: InputDecoration( - labelText: 'description'.tr(), - labelStyle: TextStyle(fontWeight: FontWeight.bold, color: colorScheme.primary), + labelText: context.t.description, + labelStyle: const TextStyle(fontWeight: FontWeight.bold), floatingLabelBehavior: FloatingLabelBehavior.always, border: const OutlineInputBorder(), - hintText: 'shared_link_edit_description_hint'.tr(), + hintText: context.t.shared_link_edit_description_hint, hintStyle: const TextStyle(fontWeight: FontWeight.normal, fontSize: 14), - disabledBorder: OutlineInputBorder(borderSide: BorderSide(color: Colors.grey.withValues(alpha: 0.5))), ), onTapOutside: (_) => descriptionFocusNode.unfocus(), ); @@ -96,16 +113,14 @@ class SharedLinkEditPage extends HookConsumerWidget { Widget buildPasswordField() { return TextField( controller: passwordController, - enabled: newShareLink.value.isEmpty, autofocus: false, decoration: InputDecoration( - labelText: 'password'.tr(), - labelStyle: TextStyle(fontWeight: FontWeight.bold, color: colorScheme.primary), + labelText: context.t.password, + labelStyle: const TextStyle(fontWeight: FontWeight.bold), floatingLabelBehavior: FloatingLabelBehavior.always, border: const OutlineInputBorder(), - hintText: 'shared_link_edit_password_hint'.tr(), + hintText: context.t.shared_link_edit_password_hint, hintStyle: const TextStyle(fontWeight: FontWeight.normal, fontSize: 14), - disabledBorder: OutlineInputBorder(borderSide: BorderSide(color: Colors.grey.withValues(alpha: 0.5))), ), ); } @@ -113,18 +128,16 @@ class SharedLinkEditPage extends HookConsumerWidget { Widget buildSlugField() { return TextField( controller: slugController, - enabled: newShareLink.value.isEmpty, focusNode: slugFocusNode, textInputAction: TextInputAction.done, autofocus: false, decoration: InputDecoration( - labelText: 'custom_url'.tr(), - labelStyle: TextStyle(fontWeight: FontWeight.bold, color: colorScheme.primary), - floatingLabelBehavior: FloatingLabelBehavior.always, + labelText: slugController.text.isNotEmpty ? context.t.custom_url : null, + labelStyle: const TextStyle(fontWeight: FontWeight.bold), border: const OutlineInputBorder(), - hintText: 'custom_url'.tr(), - hintStyle: const TextStyle(fontWeight: FontWeight.normal, fontSize: 14), - disabledBorder: OutlineInputBorder(borderSide: BorderSide(color: Colors.grey.withValues(alpha: 0.5))), + hintText: context.t.custom_url, + prefixText: slugController.text.isNotEmpty ? '/s/' : null, + prefixStyle: const TextStyle(fontWeight: FontWeight.bold, fontSize: 14), ), onTapOutside: (_) => slugFocusNode.unfocus(), ); @@ -133,145 +146,182 @@ class SharedLinkEditPage extends HookConsumerWidget { Widget buildShowMetaButton() { return SwitchListTile.adaptive( value: showMetadata.value, - onChanged: newShareLink.value.isEmpty ? (value) => showMetadata.value = value : null, - activeThumbColor: colorScheme.primary, + onChanged: (value) => showMetadata.value = value, dense: true, - title: Text("show_metadata", style: themeData.textTheme.labelLarge?.copyWith(fontWeight: FontWeight.bold)).tr(), + title: Text( + context.t.show_metadata, + style: themeData.textTheme.labelLarge?.copyWith(fontWeight: FontWeight.bold), + ), ); } Widget buildAllowDownloadButton() { return SwitchListTile.adaptive( value: allowDownload.value, - onChanged: newShareLink.value.isEmpty ? (value) => allowDownload.value = value : null, - activeThumbColor: colorScheme.primary, + onChanged: (value) => allowDownload.value = value, dense: true, title: Text( - "allow_public_user_to_download", + context.t.allow_public_user_to_download, style: themeData.textTheme.labelLarge?.copyWith(fontWeight: FontWeight.bold), - ).tr(), + ), ); } Widget buildAllowUploadButton() { return SwitchListTile.adaptive( value: allowUpload.value, - onChanged: newShareLink.value.isEmpty ? (value) => allowUpload.value = value : null, - activeThumbColor: colorScheme.primary, + onChanged: (value) => allowUpload.value = value, dense: true, title: Text( - "allow_public_user_to_upload", + context.t.allow_public_user_to_upload, style: themeData.textTheme.labelLarge?.copyWith(fontWeight: FontWeight.bold), - ).tr(), + ), ); } - Widget buildEditExpiryButton() { - return SwitchListTile.adaptive( - value: editExpiry.value, - onChanged: newShareLink.value.isEmpty ? (value) => editExpiry.value = value : null, - activeThumbColor: colorScheme.primary, - dense: true, - title: Text( - "change_expiration_time", - style: themeData.textTheme.labelLarge?.copyWith(fontWeight: FontWeight.bold), - ).tr(), + String formatDateTime(DateTime dateTime) => DateFormat.yMMMd(context.locale.toString()).add_Hm().format(dateTime); + + DateTime? getExpiresAtFromPreset(Duration preset) => preset == Duration.zero ? null : DateTime.now().add(preset); + + Future selectDate() async { + final today = DateTime.now(); + final safeInitialDate = expiryAfter.value ?? today.add(const Duration(days: 7)); + final initialDate = safeInitialDate.isBefore(today) ? today : safeInitialDate; + + final selectedDate = await showDatePicker( + context: context, + initialDate: initialDate, + firstDate: today, + lastDate: today.add(const Duration(days: maxFutureDate)), ); + + if (selectedDate != null && context.mounted) { + final isToday = + selectedDate.year == today.year && selectedDate.month == today.month && selectedDate.day == today.day; + final initialTime = isToday ? TimeOfDay.fromDateTime(today) : const TimeOfDay(hour: 12, minute: 0); + + final selectedTime = await showTimePicker(context: context, initialTime: initialTime); + + if (selectedTime != null) { + final now = DateTime.now(); + var finalDateTime = DateTime( + selectedDate.year, + selectedDate.month, + selectedDate.day, + selectedTime.hour, + selectedTime.minute, + ); + + if (finalDateTime.isBefore(now) && isToday) { + finalDateTime = now; + } + + selectedPresetIndex.value = null; + expiryAfter.value = finalDateTime; + } + } } Widget buildExpiryAfterButton() { - return DropdownMenu( - label: Text( - "expire_after", - style: TextStyle(fontWeight: FontWeight.bold, color: colorScheme.primary), - ).tr(), - enableSearch: false, - enableFilter: false, - width: context.width - 40, - initialSelection: expiryAfter.value, - enabled: newShareLink.value.isEmpty && (existingLink == null || editExpiry.value), - onSelected: (value) { - expiryAfter.value = value!; - }, - dropdownMenuEntries: [ - DropdownMenuEntry(value: 0, label: "never".tr()), - DropdownMenuEntry( - value: 30, - label: "shared_link_edit_expire_after_option_minutes".tr(namedArgs: {'count': "30"}), - ), - DropdownMenuEntry(value: 60, label: "shared_link_edit_expire_after_option_hour".tr()), - DropdownMenuEntry( - value: 60 * 6, - label: "shared_link_edit_expire_after_option_hours".tr(namedArgs: {'count': "6"}), - ), - DropdownMenuEntry(value: 60 * 24, label: "shared_link_edit_expire_after_option_day".tr()), - DropdownMenuEntry( - value: 60 * 24 * 7, - label: "shared_link_edit_expire_after_option_days".tr(namedArgs: {'count': "7"}), - ), - DropdownMenuEntry( - value: 60 * 24 * 30, - label: "shared_link_edit_expire_after_option_days".tr(namedArgs: {'count': "30"}), - ), - DropdownMenuEntry( - value: 60 * 24 * 30 * 3, - label: "shared_link_edit_expire_after_option_months".tr(namedArgs: {'count': "3"}), - ), - DropdownMenuEntry( - value: 60 * 24 * 30 * 12, - label: "shared_link_edit_expire_after_option_year".tr(namedArgs: {'count': "1"}), - ), - ], - ); - } - - void copyLinkToClipboard() { - Clipboard.setData(ClipboardData(text: newShareLink.value)).then((_) { - context.scaffoldMessenger.showSnackBar( - SnackBar( - content: Text( - "shared_link_clipboard_copied_massage", - style: context.textTheme.bodyLarge?.copyWith(color: context.primaryColor), - ).tr(), - duration: const Duration(seconds: 2), - ), - ); - }); - } - - Widget buildNewLinkField() { - return Column( + return ExpansionTile( + title: Text( + context.t.expire_after, + style: themeData.textTheme.labelLarge?.copyWith(fontWeight: FontWeight.bold), + ), + subtitle: Text( + expiryAfter.value == null ? context.t.shared_link_expires_never : formatDateTime(expiryAfter.value!), + style: TextStyle(color: themeData.colorScheme.primary), + ), children: [ - const Padding(padding: EdgeInsets.only(top: 20, bottom: 20), child: Divider()), - TextFormField( - readOnly: true, - initialValue: newShareLink.value, - decoration: InputDecoration( - border: const OutlineInputBorder(), - enabledBorder: themeData.inputDecorationTheme.focusedBorder, - suffixIcon: IconButton(onPressed: copyLinkToClipboard, icon: const Icon(Icons.copy)), - ), - ), Padding( - padding: const EdgeInsets.only(top: 16.0), - child: Align( - alignment: Alignment.bottomRight, - child: ElevatedButton( - onPressed: () { - context.maybePop(); - }, - child: const Text("done", style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold)).tr(), - ), + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Wrap( + spacing: 8, + runSpacing: 8, + children: List.generate(expiryPresets.length, (index) { + final preset = expiryPresets[index]; + return ChoiceChip( + label: Text(preset.$2), + selected: selectedPresetIndex.value == index, + onSelected: (_) { + selectedPresetIndex.value = index; + expiryAfter.value = getExpiresAtFromPreset(preset.$1); + }, + ); + }), + ), + if (expiryAfter.value != null) ...[ + const SizedBox(height: 16), + SizedBox( + width: double.infinity, + child: OutlinedButton.icon( + onPressed: selectDate, + icon: const Icon(Icons.edit_calendar), + label: Text(context.t.edit_date_and_time), + ), + ), + ], + ], ), ), ], ); } - DateTime calculateExpiry() { - return DateTime.now().add(Duration(minutes: expiryAfter.value)); + Future copyToClipboard(String link) async { + await Clipboard.setData(ClipboardData(text: link)); + if (!context.mounted) { + return; + } + context.scaffoldMessenger.showSnackBar( + SnackBar( + content: Text( + context.t.shared_link_clipboard_copied_massage, + style: context.textTheme.bodyLarge?.copyWith(color: context.primaryColor), + ), + duration: const Duration(seconds: 2), + ), + ); } + Widget buildLinkCopyField(String link) { + return TextFormField( + readOnly: true, + onTap: () => copyToClipboard(link), + initialValue: link, + decoration: InputDecoration( + border: const OutlineInputBorder(), + enabledBorder: themeData.inputDecorationTheme.focusedBorder, + suffixIcon: IconButton(onPressed: () => Share.share(link), icon: const Icon(Icons.share)), + ), + ); + } + + Widget buildNewLinkReadyScreen() { + return Padding( + padding: const EdgeInsets.all(20), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.add_link, size: 100, color: themeData.colorScheme.primary), + const SizedBox(height: 20), + buildLinkCopyField(newShareLink.value), + const SizedBox(height: 20), + ElevatedButton.icon( + onPressed: () => context.maybePop(), + icon: const Icon(Icons.check), + label: Text(context.t.done, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold)), + ), + ], + ), + ); + } + + DateTime? calculateExpiry() => expiryAfter.value; + Future handleNewLink() async { final newLink = await ref .read(sharedLinkServiceProvider) @@ -284,30 +334,30 @@ class SharedLinkEditPage extends HookConsumerWidget { description: descriptionController.text.isEmpty ? null : descriptionController.text, password: passwordController.text.isEmpty ? null : passwordController.text, slug: slugController.text.isEmpty ? null : slugController.text, - expiresAt: expiryAfter.value == 0 ? null : calculateExpiry(), + expiresAt: calculateExpiry()?.toUtc(), ); + if (!context.mounted) { + return; + } ref.invalidate(sharedLinksStateProvider); await ref.read(serverInfoProvider.notifier).getServerConfig(); + if (!context.mounted) { + return; + } final externalDomain = ref.read(serverInfoProvider.select((s) => s.serverConfig.externalDomain)); - var serverUrl = externalDomain.isNotEmpty ? externalDomain : getServerUrl(); - if (serverUrl != null && !serverUrl.endsWith('/')) { - serverUrl += '/'; - } + final serverUrl = externalDomain.isNotEmpty ? externalDomain : getServerUrl(); - if (newLink != null && serverUrl != null) { - final hasSlug = newLink.slug?.isNotEmpty == true; - final urlPath = hasSlug ? newLink.slug : newLink.key; - final basePath = hasSlug ? 's' : 'share'; - newShareLink.value = "$serverUrl$basePath/$urlPath"; - copyLinkToClipboard(); - } else if (newLink == null) { + if (newLink != null) { + newShareLink.value = buildSharedLinkUrl(baseUrl: serverUrl, slug: newLink.slug, key: newLink.key) ?? ''; + await copyToClipboard(newShareLink.value); + } else { ImmichToast.show( context: context, gravity: ToastGravity.BOTTOM, toastType: ToastType.error, - msg: 'shared_link_create_error'.tr(), + msg: context.t.shared_link_create_error, ); } } @@ -316,11 +366,10 @@ class SharedLinkEditPage extends HookConsumerWidget { bool? download; bool? upload; bool? meta; - String? desc; - String? password; + var password = const Optional.absent(); + var description = const Optional.absent(); String? slug; - DateTime? expiry; - bool? changeExpiry; + var expiry = const Optional.absent(); if (allowDownload.value != existingLink!.allowDownload) { download = allowDownload.value; @@ -334,12 +383,16 @@ class SharedLinkEditPage extends HookConsumerWidget { meta = showMetadata.value; } - if (descriptionController.text != existingLink!.description) { - desc = descriptionController.text; + if (descriptionController.text != (existingLink!.description ?? '')) { + description = descriptionController.text.isEmpty + ? const Optional.present(null) + : Optional.present(descriptionController.text); } - if (passwordController.text != existingLink!.password) { - password = passwordController.text; + if (passwordController.text != (existingLink!.password ?? '')) { + password = passwordController.text.isEmpty + ? const Optional.present(null) + : Optional.present(passwordController.text); } if (slugController.text != (existingLink!.slug ?? "")) { @@ -348,9 +401,9 @@ class SharedLinkEditPage extends HookConsumerWidget { slug = existingLink!.slug; } - if (editExpiry.value) { - expiry = expiryAfter.value == 0 ? null : calculateExpiry(); - changeExpiry = true; + final newExpiry = expiryAfter.value; + if (newExpiry?.toUtc() != existingLink!.expiresAt?.toUtc()) { + expiry = newExpiry == null ? const Optional.present(null) : Optional.present(newExpiry.toUtc()); } await ref @@ -360,72 +413,117 @@ class SharedLinkEditPage extends HookConsumerWidget { showMeta: meta, allowDownload: download, allowUpload: upload, - description: desc, + description: description, password: password, slug: slug, expiresAt: expiry, - changeExpiry: changeExpiry, ); + if (!context.mounted) { + return; + } ref.invalidate(sharedLinksStateProvider); await context.maybePop(); } + Future handleDeleteLink() async { + return showDialog( + context: context, + builder: (BuildContext context) => ConfirmDialog( + title: "delete_shared_link_dialog_title", + content: "confirm_delete_shared_link", + onOk: () async { + await ref.read(sharedLinkServiceProvider).deleteSharedLink(existingLink!.id); + ref.invalidate(sharedLinksStateProvider); + if (context.mounted) { + await context.maybePop(); + } + }, + ), + ); + } + return Scaffold( appBar: AppBar( - title: Text(existingLink == null ? "create_link_to_share" : "edit_link").tr(), + title: Text(existingLink == null ? context.t.create_link_to_share : context.t.edit_link), elevation: 0, leading: const CloseButton(), centerTitle: false, ), body: SafeArea( - child: ListView( - children: [ - Padding(padding: const EdgeInsets.all(padding), child: buildLinkTitle()), - Padding(padding: const EdgeInsets.all(padding), child: buildDescriptionField()), - Padding(padding: const EdgeInsets.all(padding), child: buildPasswordField()), - Padding(padding: const EdgeInsets.all(padding), child: buildSlugField()), - Padding( - padding: const EdgeInsets.only(left: padding, right: padding, bottom: padding), - child: buildShowMetaButton(), - ), - Padding( - padding: const EdgeInsets.only(left: padding, right: padding, bottom: padding), - child: buildAllowDownloadButton(), - ), - Padding( - padding: const EdgeInsets.only(left: padding, right: 20, bottom: 20), - child: buildAllowUploadButton(), - ), - if (existingLink != null) - Padding( - padding: const EdgeInsets.only(left: padding, right: padding, bottom: padding), - child: buildEditExpiryButton(), - ), - Padding( - padding: const EdgeInsets.only(left: padding, right: padding, bottom: padding), - child: buildExpiryAfterButton(), - ), - if (newShareLink.value.isEmpty) - Align( - alignment: Alignment.bottomRight, - child: Padding( - padding: const EdgeInsets.only(right: padding + 10, bottom: padding), - child: ElevatedButton( - onPressed: existingLink != null ? handleEditLink : handleNewLink, - child: Text( - existingLink != null ? "shared_link_edit_submit_button" : "create_link", - style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold), - ).tr(), - ), + child: newShareLink.value.isEmpty + ? Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: ListView( + children: [ + const SizedBox(height: 20), + buildLinkTitle(), + if (existingLink != null) + Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const SizedBox(height: 16), + buildLinkCopyField( + buildSharedLinkUrl( + baseUrl: displayServerUrl, + slug: existingLink!.slug, + key: existingLink!.key, + ) ?? + '', + ), + const SizedBox(height: 24), + const Divider(), + ], + ), + const SizedBox(height: 24), + buildDescriptionField(), + const SizedBox(height: 16), + buildPasswordField(), + const SizedBox(height: 16), + buildSlugField(), + const SizedBox(height: 16), + buildShowMetaButton(), + const SizedBox(height: 16), + buildAllowDownloadButton(), + const SizedBox(height: 16), + buildAllowUploadButton(), + const SizedBox(height: 16), + buildExpiryAfterButton(), + const SizedBox(height: 24), + Align( + alignment: Alignment.centerRight, + child: Row( + mainAxisSize: MainAxisSize.min, + spacing: 8, + children: [ + if (existingLink != null) + OutlinedButton.icon( + style: OutlinedButton.styleFrom( + foregroundColor: themeData.colorScheme.error, + side: BorderSide(color: themeData.colorScheme.error), + ), + onPressed: handleDeleteLink, + icon: const Icon(Icons.delete_outline), + label: Text( + context.t.delete, + style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold), + ), + ), + ElevatedButton.icon( + icon: const Icon(Icons.check), + onPressed: existingLink != null ? handleEditLink : handleNewLink, + label: Text( + existingLink != null ? context.t.shared_link_edit_submit_button : context.t.create_link, + style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold), + ), + ), + ], + ), + ), + const SizedBox(height: 40), + ], ), - ), - if (newShareLink.value.isNotEmpty) - Padding( - padding: const EdgeInsets.only(left: padding, right: padding, bottom: padding), - child: buildNewLinkField(), - ), - ], - ), + ) + : Center(child: buildNewLinkReadyScreen()), ), ); } diff --git a/mobile/lib/platform/background_worker_api.g.dart b/mobile/lib/platform/background_worker_api.g.dart index 580531b0f0..34f4c41b48 100644 --- a/mobile/lib/platform/background_worker_api.g.dart +++ b/mobile/lib/platform/background_worker_api.g.dart @@ -277,7 +277,7 @@ abstract class BackgroundWorkerFlutterApi { Future onIosUpload(bool isRefresh, int? maxSeconds); - Future onAndroidUpload(); + Future onAndroidUpload(int? maxMinutes); Future cancel(); @@ -323,8 +323,10 @@ abstract class BackgroundWorkerFlutterApi { pigeonVar_channel.setMessageHandler(null); } else { pigeonVar_channel.setMessageHandler((Object? message) async { + final List args = message! as List; + final int? arg_maxMinutes = args[0] as int?; try { - await api.onAndroidUpload(); + await api.onAndroidUpload(arg_maxMinutes); return wrapResponse(empty: true); } on PlatformException catch (e) { return wrapResponse(error: e); diff --git a/mobile/lib/platform/native_sync_api.g.dart b/mobile/lib/platform/native_sync_api.g.dart index e7095663b0..bd979af87b 100644 --- a/mobile/lib/platform/native_sync_api.g.dart +++ b/mobile/lib/platform/native_sync_api.g.dart @@ -635,6 +635,20 @@ class NativeSyncApi { _extractReplyValueOrThrow(pigeonVar_replyList, pigeonVar_channelName, isNullValid: true); } + Future cancelSync() async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.NativeSyncApi.cancelSync$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + _extractReplyValueOrThrow(pigeonVar_replyList, pigeonVar_channelName, isNullValid: true); + } + Future>> getTrashedAssets() async { final pigeonVar_channelName = 'dev.flutter.pigeon.immich_mobile.NativeSyncApi.getTrashedAssets$pigeonVar_messageChannelSuffix'; @@ -654,6 +668,25 @@ class NativeSyncApi { return (pigeonVar_replyValue! as Map).cast>(); } + Future restoreFromTrashById(String mediaId, int type) async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.NativeSyncApi.restoreFromTrashById$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([mediaId, type]); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ); + return pigeonVar_replyValue! as bool; + } + Future> getCloudIdForAssetIds(List assetIds) async { final pigeonVar_channelName = 'dev.flutter.pigeon.immich_mobile.NativeSyncApi.getCloudIdForAssetIds$pigeonVar_messageChannelSuffix'; diff --git a/mobile/lib/platform/network_api.g.dart b/mobile/lib/platform/network_api.g.dart index 7fab476694..6258060bfb 100644 --- a/mobile/lib/platform/network_api.g.dart +++ b/mobile/lib/platform/network_api.g.dart @@ -309,4 +309,23 @@ class NetworkApi { _extractReplyValueOrThrow(pigeonVar_replyList, pigeonVar_channelName, isNullValid: true); } + + Future getAppGroupId() async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.NetworkApi.getAppGroupId$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ); + return pigeonVar_replyValue! as String; + } } diff --git a/mobile/lib/platform/permission_api.g.dart b/mobile/lib/platform/permission_api.g.dart new file mode 100644 index 0000000000..d2646e482f --- /dev/null +++ b/mobile/lib/platform/permission_api.g.dart @@ -0,0 +1,119 @@ +// Autogenerated from Pigeon (v26.3.4), do not edit directly. +// See also: https://pub.dev/packages/pigeon +// ignore_for_file: unused_import, unused_shown_name +// ignore_for_file: type=lint + +import 'dart:async'; +import 'dart:typed_data' show Float64List, Int32List, Int64List; + +import 'package:flutter/services.dart'; +import 'package:meta/meta.dart' show immutable, protected, visibleForTesting; + +Object? _extractReplyValueOrThrow(List? replyList, String channelName, {required bool isNullValid}) { + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel: "$channelName".', + ); + } else if (replyList.length > 1) { + throw PlatformException(code: replyList[0]! as String, message: replyList[1] as String?, details: replyList[2]); + } else if (!isNullValid && (replyList.isNotEmpty && replyList[0] == null)) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } + return replyList.firstOrNull; +} + +class _PigeonCodec extends StandardMessageCodec { + const _PigeonCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is int) { + buffer.putUint8(4); + buffer.putInt64(value); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + default: + return super.readValueOfType(type, buffer); + } + } +} + +class PermissionApi { + /// Constructor for [PermissionApi]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + PermissionApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) + : pigeonVar_binaryMessenger = binaryMessenger, + pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + final BinaryMessenger? pigeonVar_binaryMessenger; + + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); + + final String pigeonVar_messageChannelSuffix; + + Future hasManageMediaPermission() async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.PermissionApi.hasManageMediaPermission$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ); + return pigeonVar_replyValue! as bool; + } + + Future requestManageMediaPermission() async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.PermissionApi.requestManageMediaPermission$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ); + return pigeonVar_replyValue! as bool; + } + + Future manageMediaPermission() async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.PermissionApi.manageMediaPermission$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ); + return pigeonVar_replyValue! as bool; + } +} diff --git a/mobile/lib/platform/view_intent_api.g.dart b/mobile/lib/platform/view_intent_api.g.dart new file mode 100644 index 0000000000..d457c249de --- /dev/null +++ b/mobile/lib/platform/view_intent_api.g.dart @@ -0,0 +1,191 @@ +// Autogenerated from Pigeon (v26.3.4), do not edit directly. +// See also: https://pub.dev/packages/pigeon +// ignore_for_file: unused_import, unused_shown_name +// ignore_for_file: type=lint + +import 'dart:async'; +import 'dart:typed_data' show Float64List, Int32List, Int64List; + +import 'package:flutter/services.dart'; +import 'package:meta/meta.dart' show immutable, protected, visibleForTesting; + +Object? _extractReplyValueOrThrow(List? replyList, String channelName, {required bool isNullValid}) { + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel: "$channelName".', + ); + } else if (replyList.length > 1) { + throw PlatformException(code: replyList[0]! as String, message: replyList[1] as String?, details: replyList[2]); + } else if (!isNullValid && (replyList.isNotEmpty && replyList[0] == null)) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } + return replyList.firstOrNull; +} + +bool _deepEquals(Object? a, Object? b) { + if (identical(a, b)) { + return true; + } + if (a is double && b is double) { + if (a.isNaN && b.isNaN) { + return true; + } + return a == b; + } + if (a is List && b is List) { + return a.length == b.length && a.indexed.every(((int, dynamic) item) => _deepEquals(item.$2, b[item.$1])); + } + if (a is Map && b is Map) { + if (a.length != b.length) { + return false; + } + for (final MapEntry entryA in a.entries) { + bool found = false; + for (final MapEntry entryB in b.entries) { + if (_deepEquals(entryA.key, entryB.key)) { + if (_deepEquals(entryA.value, entryB.value)) { + found = true; + break; + } else { + return false; + } + } + } + if (!found) { + return false; + } + } + return true; + } + return a == b; +} + +int _deepHash(Object? value) { + if (value is List) { + return Object.hashAll(value.map(_deepHash)); + } + if (value is Map) { + int result = 0; + for (final MapEntry entry in value.entries) { + result += (_deepHash(entry.key) * 31) ^ _deepHash(entry.value); + } + return result; + } + if (value is double && value.isNaN) { + // Normalize NaN to a consistent hash. + return 0x7FF8000000000000.hashCode; + } + if (value is double && value == 0.0) { + // Normalize -0.0 to 0.0 so they have the same hash code. + return 0.0.hashCode; + } + return value.hashCode; +} + +class ViewIntentPayload { + ViewIntentPayload({this.path, required this.mimeType, this.localAssetId}); + + String? path; + + String mimeType; + + String? localAssetId; + + List _toList() { + return [path, mimeType, localAssetId]; + } + + Object encode() { + return _toList(); + } + + static ViewIntentPayload decode(Object result) { + result as List; + return ViewIntentPayload( + path: result[0] as String?, + mimeType: result[1]! as String, + localAssetId: result[2] as String?, + ); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + bool operator ==(Object other) { + if (other is! ViewIntentPayload || other.runtimeType != runtimeType) { + return false; + } + if (identical(this, other)) { + return true; + } + return _deepEquals(path, other.path) && + _deepEquals(mimeType, other.mimeType) && + _deepEquals(localAssetId, other.localAssetId); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + int get hashCode => _deepHash([runtimeType, ..._toList()]); +} + +class _PigeonCodec extends StandardMessageCodec { + const _PigeonCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is int) { + buffer.putUint8(4); + buffer.putInt64(value); + } else if (value is ViewIntentPayload) { + buffer.putUint8(129); + writeValue(buffer, value.encode()); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + case 129: + return ViewIntentPayload.decode(readValue(buffer)!); + default: + return super.readValueOfType(type, buffer); + } + } +} + +class ViewIntentHostApi { + /// Constructor for [ViewIntentHostApi]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + ViewIntentHostApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) + : pigeonVar_binaryMessenger = binaryMessenger, + pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + final BinaryMessenger? pigeonVar_binaryMessenger; + + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); + + final String pigeonVar_messageChannelSuffix; + + Future consumeViewIntent() async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.ViewIntentHostApi.consumeViewIntent$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: true, + ); + return pigeonVar_replyValue as ViewIntentPayload?; + } +} diff --git a/mobile/lib/presentation/pages/drift_album.page.dart b/mobile/lib/presentation/pages/drift_album.page.dart index c9fed636b4..47a4625f87 100644 --- a/mobile/lib/presentation/pages/drift_album.page.dart +++ b/mobile/lib/presentation/pages/drift_album.page.dart @@ -37,6 +37,7 @@ class _DriftAlbumsPageState extends ConsumerState { final scrollView = CustomScrollView( controller: _scrollController, + physics: const AlwaysScrollableScrollPhysics(), slivers: [ ImmichSliverAppBar( snap: false, diff --git a/mobile/lib/presentation/pages/drift_asset_selection_timeline.page.dart b/mobile/lib/presentation/pages/drift_asset_selection_timeline.page.dart index 19f813cdb5..a12ca4932b 100644 --- a/mobile/lib/presentation/pages/drift_asset_selection_timeline.page.dart +++ b/mobile/lib/presentation/pages/drift_asset_selection_timeline.page.dart @@ -5,7 +5,6 @@ import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; -import 'package:immich_mobile/providers/user.provider.dart'; @RoutePage() class DriftAssetSelectionTimelinePage extends ConsumerWidget { @@ -22,17 +21,13 @@ class DriftAssetSelectionTimelinePage extends ConsumerWidget { ), ), timelineServiceProvider.overrideWith((ref) { - final user = ref.watch(currentUserProvider); - if (user == null) { - throw Exception('User must be logged in to access asset selection timeline'); - } - - final timelineService = ref.watch(timelineFactoryProvider).remoteAssets(user.id); + final timelineUsers = ref.watch(timelineUsersProvider).valueOrNull ?? []; + final timelineService = ref.watch(timelineFactoryProvider).main(timelineUsers); ref.onDispose(timelineService.dispose); return timelineService; }), ], - child: const Timeline(), + child: const Timeline(showStorageIndicator: true), ); } } diff --git a/mobile/lib/presentation/pages/drift_asset_troubleshoot.page.dart b/mobile/lib/presentation/pages/drift_asset_troubleshoot.page.dart index f5ceba6d0e..20021bada1 100644 --- a/mobile/lib/presentation/pages/drift_asset_troubleshoot.page.dart +++ b/mobile/lib/presentation/pages/drift_asset_troubleshoot.page.dart @@ -191,8 +191,12 @@ class _AssetPropertiesSectionState extends ConsumerState<_AssetPropertiesSection } String _getAssetTypeTitle(BaseAsset asset) { - if (asset is LocalAsset) return 'Local Asset'; - if (asset is RemoteAsset) return 'Remote Asset'; + if (asset is LocalAsset) { + return 'Local Asset'; + } + if (asset is RemoteAsset) { + return 'Remote Asset'; + } return 'Base Asset'; } } diff --git a/mobile/lib/presentation/pages/drift_create_album.page.dart b/mobile/lib/presentation/pages/drift_create_album.page.dart index f1cbdb13ff..e6ff10fd59 100644 --- a/mobile/lib/presentation/pages/drift_create_album.page.dart +++ b/mobile/lib/presentation/pages/drift_create_album.page.dart @@ -179,17 +179,14 @@ class _DriftCreateAlbumPageState extends ConsumerState { } final album = await ref - .watch(remoteAlbumProvider.notifier) - .createAlbum( + .read(remoteAlbumProvider.notifier) + .createAlbumWithAssets( title: title, description: albumDescriptionController.text.trim(), - assetIds: selectedAssets.map((asset) { - final remoteAsset = asset as RemoteAsset; - return remoteAsset.id; - }).toList(), + assets: selectedAssets, ); - if (album != null) { + if (album != null && context.mounted) { unawaited(context.replaceRoute(RemoteAlbumRoute(album: album))); } } diff --git a/mobile/lib/presentation/pages/drift_memory.page.dart b/mobile/lib/presentation/pages/drift_memory.page.dart index 846f062501..6919925d55 100644 --- a/mobile/lib/presentation/pages/drift_memory.page.dart +++ b/mobile/lib/presentation/pages/drift_memory.page.dart @@ -160,12 +160,25 @@ class DriftMemoryPage extends HookConsumerWidget { currentAssetPage.value = otherIndex; updateProgressText(); + final activeMemory = currentMemory.value; + // Wait for page change animation to finish await Future.delayed(const Duration(milliseconds: 400)); + + // check if memory is still the same and if context is still mounted + if (currentMemory.value != activeMemory || !context.mounted) { + return; + } + // And then precache the next asset await precacheAsset(otherIndex + 1); - final asset = currentMemory.value.assets[otherIndex]; + // check again as precache involves async operations + if (currentMemory.value != activeMemory || !context.mounted) { + return; + } + + final asset = activeMemory.assets[otherIndex]; currentAsset.value = asset; ref.read(assetViewerProvider.notifier).setAsset(asset); } diff --git a/mobile/lib/presentation/pages/drift_recently_added.page.dart b/mobile/lib/presentation/pages/drift_recently_added.page.dart new file mode 100644 index 0000000000..c33cd4370d --- /dev/null +++ b/mobile/lib/presentation/pages/drift_recently_added.page.dart @@ -0,0 +1,32 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/widgets.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart'; +import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; +import 'package:immich_mobile/providers/user.provider.dart'; +import 'package:immich_mobile/widgets/common/mesmerizing_sliver_app_bar.dart'; + +@RoutePage() +class DriftRecentlyAddedPage extends StatelessWidget { + const DriftRecentlyAddedPage({super.key}); + + @override + Widget build(BuildContext context) { + return ProviderScope( + overrides: [ + timelineServiceProvider.overrideWith((ref) { + final user = ref.watch(currentUserProvider); + if (user == null) { + throw Exception('User must be logged in to access recently taken'); + } + + final timelineService = ref.watch(timelineFactoryProvider).recentlyAdded(user.id); + ref.onDispose(timelineService.dispose); + return timelineService; + }), + ], + child: Timeline(appBar: MesmerizingSliverAppBar(title: 'recently_added'.t())), + ); + } +} diff --git a/mobile/lib/presentation/pages/drift_remote_album.page.dart b/mobile/lib/presentation/pages/drift_remote_album.page.dart index ba9ccf2ffd..5a5b49a2df 100644 --- a/mobile/lib/presentation/pages/drift_remote_album.page.dart +++ b/mobile/lib/presentation/pages/drift_remote_album.page.dart @@ -8,6 +8,7 @@ import 'package:immich_mobile/domain/models/album/album.model.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/presentation/widgets/album/pending_uploads_banner.widget.dart'; import 'package:immich_mobile/presentation/widgets/bottom_sheet/remote_album_bottom_sheet.widget.dart'; import 'package:immich_mobile/presentation/widgets/remote_album/drift_album_option.widget.dart'; import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart'; @@ -19,6 +20,7 @@ import 'package:immich_mobile/providers/user.provider.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; import 'package:immich_mobile/widgets/common/remote_album_sliver_app_bar.dart'; +import 'package:openapi/api.dart' show Optional; @RoutePage() class RemoteAlbumPage extends ConsumerStatefulWidget { @@ -39,7 +41,8 @@ class _RemoteAlbumPageState extends ConsumerState { } Future addAssets(BuildContext context) async { - final albumAssets = await ref.read(remoteAlbumProvider.notifier).getAssets(_album.id); + final notifier = ref.read(remoteAlbumProvider.notifier); + final albumAssets = await notifier.getAssets(_album.id); final newAssets = await context.pushRoute>( DriftAssetSelectionTimelineRoute(lockedSelectionAssets: albumAssets.toSet()), @@ -49,17 +52,9 @@ class _RemoteAlbumPageState extends ConsumerState { return; } - final added = await ref - .read(remoteAlbumProvider.notifier) - .addAssets( - _album.id, - newAssets.map((asset) { - final remoteAsset = asset as RemoteAsset; - return remoteAsset.id; - }).toList(), - ); + final added = await notifier.addAssetsToAlbum(_album.id, newAssets); - if (added > 0) { + if (added > 0 && context.mounted) { ImmichToast.show( context: context, msg: "assets_added_to_album_count".t(context: context, args: {'count': added.toString()}), @@ -186,6 +181,7 @@ class _RemoteAlbumPageState extends ConsumerState { currentRemoteAlbumScopedProvider.overrideWithValue(_album), ], child: Timeline( + topSliverWidget: PendingUploadsBanner(albumId: _album.id), appBar: RemoteAlbumSliverAppBar( icon: Icons.photo_album_outlined, kebabMenu: _AlbumKebabMenu( @@ -245,15 +241,20 @@ class _EditAlbumDialogState extends ConsumerState<_EditAlbumDialog> { } Future _handleSave() async { - if (formKey.currentState?.validate() != true) return; + if (formKey.currentState?.validate() != true) { + return; + } try { final newTitle = titleController.text.trim(); final newDescription = descriptionController.text.trim(); + final description = newDescription.isEmpty + ? const Optional.present(null) + : Optional.present(newDescription); await ref .read(remoteAlbumProvider.notifier) - .updateAlbum(widget.album.id, name: newTitle, description: newDescription); + .updateAlbum(widget.album.id, name: newTitle, description: description); if (mounted) { Navigator.of( diff --git a/mobile/lib/presentation/pages/drift_slideshow.page.dart b/mobile/lib/presentation/pages/drift_slideshow.page.dart new file mode 100644 index 0000000000..3a5f95554c --- /dev/null +++ b/mobile/lib/presentation/pages/drift_slideshow.page.dart @@ -0,0 +1,376 @@ +import 'dart:async'; +import 'dart:math'; +import 'dart:ui'; + +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/domain/models/config/slideshow_config.dart'; +import 'package:immich_mobile/domain/services/timeline.service.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/extensions/scroll_extensions.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/pages/common/settings.page.dart'; +import 'package:immich_mobile/presentation/widgets/asset_viewer/video_viewer.widget.dart'; +import 'package:immich_mobile/presentation/widgets/images/image_provider.dart'; +import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'; +import 'package:immich_mobile/providers/asset_viewer/video_player_provider.dart'; +import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; +import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/widgets/common/immich_loading_indicator.dart'; +import 'package:immich_mobile/widgets/photo_view/photo_view.dart'; +import 'package:wakelock_plus/wakelock_plus.dart'; + +@RoutePage() +class DriftSlideshowPage extends ConsumerStatefulWidget { + final TimelineService timeline; + + const DriftSlideshowPage({super.key, required this.timeline}); + + @override + ConsumerState createState() => _DriftSlideshowPageState(); +} + +class _DriftSlideshowPageState extends ConsumerState { + late SlideshowConfig _config; + late final PageController _pageController; + late final Stopwatch _stopwatch; + late Timer _timer; + late int _index; + late int _nextIndex; + bool _paused = false; + bool _showAppBar = false; + + @override + initState() { + super.initState(); + _config = ref.read(appConfigProvider.select((s) => s.slideshow)); + final asset = ref.read(assetViewerProvider).currentAsset; + _index = asset == null ? 0 : widget.timeline.getIndex(asset.heroTag) ?? 0; + _pageController = PageController(initialPage: _index); + _stopwatch = Stopwatch(); + _createTimer(); + _updateNextIndex(); + ref.listenManual(appConfigProvider.select((s) => s.slideshow), _onConfigChanged); + + SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive); + unawaited(WakelockPlus.enable()); + } + + @override + dispose() { + _timer.cancel(); + _stopwatch.stop(); + _pageController.dispose(); + unawaited(WakelockPlus.disable()); + SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); + super.dispose(); + } + + void _play() { + final asset = widget.timeline.getAssetSafe(_index)!; + + if (asset.isImage) { + _createTimer(); + } else if (ref.read(videoPlayerProvider(asset.heroTag)).status == VideoPlaybackStatus.paused) { + ref.read(videoPlayerProvider(asset.heroTag).notifier).play(); + } else { + _nextPage(); + } + + _updateNextIndex(); + + setState(() { + _paused = false; + }); + } + + void _pause() { + _timer.cancel(); + _stopwatch.stop(); + + final asset = widget.timeline.getAssetSafe(_index)!; + + if (!asset.isImage) { + ref.read(videoPlayerProvider(asset.heroTag).notifier).pause(); + } + + setState(() { + _paused = true; + }); + } + + void _onConfigChanged(SlideshowConfig? previous, SlideshowConfig next) { + if (_config == next) { + return; + } + + final durationChanged = _config.duration != next.duration; + _config = next; + _updateNextIndex(); + + final asset = widget.timeline.getAssetSafe(_index); + if (durationChanged && !_paused && asset?.isImage == true) { + _timer.cancel(); + _createTimer(); + } + + setState(() {}); + } + + void _updateNextIndex() { + _nextIndex = switch (_config.direction) { + SlideshowDirection.forward => _index + 1, + SlideshowDirection.backward => _index - 1, + SlideshowDirection.shuffle => widget.timeline.getIndex(widget.timeline.getRandomAsset().heroTag)!, + }; + + if (!widget.timeline.hasRange(_nextIndex, 1)) { + widget.timeline.preloadAssets(_nextIndex); + } + } + + void _nextPage() async { + if (_nextIndex < 0 || _nextIndex >= widget.timeline.totalAssets) { + if (_config.repeat) { + final wrapped = _config.direction == SlideshowDirection.forward ? 0 : widget.timeline.totalAssets - 1; + await widget.timeline.preloadAssets(wrapped); + _pageController.jumpToPage(wrapped); + } else { + setState(() { + _paused = true; + }); + } + return; + } + + if (!widget.timeline.hasRange(_nextIndex, 1)) { + await widget.timeline.preloadAssets(_nextIndex); + } + + if (_config.direction == SlideshowDirection.shuffle || !_config.transition) { + _pageController.jumpToPage(_nextIndex); + } else { + unawaited(_pageController.animateToPage(_nextIndex, duration: Durations.long2, curve: Curves.easeIn)); + } + } + + void _createTimer() { + _timer = Timer(Duration(milliseconds: _config.duration * 1000 - _stopwatch.elapsedMilliseconds), () { + _stopwatch.stop(); + _stopwatch.reset(); + _nextPage(); + }); + + _stopwatch.start(); + } + + void _pageChanged(int page) { + final asset = widget.timeline.getAssetSafe(page)!; + + setState(() { + _index = page; + + if (!asset.isImage) { + _paused = false; + } + }); + + _timer.cancel(); + _stopwatch.stop(); + _stopwatch.reset(); + + if (!_paused && asset.isImage) { + _createTimer(); + } + + _updateNextIndex(); + } + + void _onTapUp() async { + await SystemChrome.setEnabledSystemUIMode(_showAppBar ? SystemUiMode.immersive : SystemUiMode.edgeToEdge); + + WidgetsBinding.instance.addPostFrameCallback((_) { + setState(() { + _showAppBar = !_showAppBar; + }); + }); + } + + Widget _getProgressBar(BuildContext context) { + final asset = widget.timeline.getAssetSafe(_index); + + if (asset == null) { + return Container(); + } + + if (asset.isImage) { + final elapsed = _stopwatch.elapsedMilliseconds; + final duration = _config.duration * 1000; + + return TweenAnimationBuilder( + key: Key(_index.toString()), + tween: Tween(begin: elapsed / duration.toDouble(), end: _paused ? elapsed / duration.toDouble() : 1.0), + duration: Duration(milliseconds: _paused ? 1 : max(duration - elapsed, 1)), + builder: (context, value, _) => LinearProgressIndicator( + color: context.colorScheme.primary, + borderRadius: const BorderRadius.all(Radius.zero), + minHeight: 5, + value: value, + ), + ); + } else { + return LinearProgressIndicator( + color: context.colorScheme.primary, + borderRadius: const BorderRadius.all(Radius.zero), + minHeight: 5, + value: + ref.watch(videoPlayerProvider(asset.heroTag).select((s) => s.position)).inMilliseconds / + asset.duration.inMilliseconds, + ); + } + } + + Widget _getBlur(BuildContext context, int index) { + final asset = widget.timeline.getAssetSafe(index); + + if (asset == null) { + return Container(); + } + + return ImageFiltered( + imageFilter: ImageFilter.blur(sigmaX: 30, sigmaY: 30), + child: Container( + decoration: BoxDecoration( + image: DecorationImage( + image: getFullImageProvider(asset, size: Size(context.width, context.height)), + fit: BoxFit.cover, + ), + ), + child: Container(color: Colors.black.withValues(alpha: 0.2)), + ), + ); + } + + Widget _getPhotoView(BuildContext context, int index) { + final asset = widget.timeline.getAssetSafe(index); + + if (asset == null) { + return const Center(child: ImmichLoadingIndicator()); + } + + final scale = _config.look == SlideshowLook.cover + ? PhotoViewComputedScale.covered + : PhotoViewComputedScale.contained; + final isCurrent = _index == index; + final imageProvider = getFullImageProvider(asset, size: context.sizeData); + + if (asset.isImage) { + final zoomOut = index % 2 == 1; + final elapsed = _stopwatch.elapsedMilliseconds; + final duration = _config.duration * 1000; + final progress = zoomOut ? 1.0 - elapsed / duration.toDouble() : elapsed / duration.toDouble(); + + return TweenAnimationBuilder( + tween: Tween( + begin: progress, + end: _paused + ? progress + : zoomOut + ? 0.0 + : 1.0, + ), + duration: Duration(milliseconds: _paused ? 1 : max(duration - elapsed, 1)), + builder: (context, value, _) => PhotoView( + imageProvider: imageProvider, + index: index, + disableScaleGestures: true, + gaplessPlayback: true, + filterQuality: FilterQuality.high, + initialScale: scale * (1.0 + value / 10.0), + controller: PhotoViewController(), + onTapUp: (_, _, _) => _onTapUp(), + ), + ); + } else { + final status = ref.watch(videoPlayerProvider(asset.heroTag).select((s) => s.status)); + final position = ref.read(videoPlayerProvider(asset.heroTag)).position; + + if (status == VideoPlaybackStatus.completed && isCurrent && position.inMicroseconds > 0) { + _nextPage(); + } else if (status == VideoPlaybackStatus.playing) { + ref.read(videoPlayerProvider(asset.heroTag).notifier).setLoop(false); + } + + return PhotoView.customChild( + onTapUp: (_, _, _) => _onTapUp(), + disableScaleGestures: true, + filterQuality: FilterQuality.high, + initialScale: scale, + child: NativeVideoViewer( + asset: asset, + isCurrent: isCurrent, + image: Image(image: imageProvider, fit: BoxFit.contain, alignment: Alignment.center), + ), + ); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: PreferredSize( + preferredSize: Size(AppBar().preferredSize.width, AppBar().preferredSize.height + 5), + child: IgnorePointer( + ignoring: !_showAppBar, + child: AnimatedOpacity( + opacity: _showAppBar ? 1.0 : 0.0, + duration: Durations.short2, + child: Column( + children: [ + AppBar( + backgroundColor: context.scaffoldBackgroundColor, + title: Text("slideshow".t(context: context)), + actions: [ + IconButton( + onPressed: _paused ? _play : _pause, + icon: Icon(_paused ? Icons.play_arrow : Icons.pause), + ), + IconButton( + onPressed: () { + _pause(); + context.pushRoute(SettingsSubRoute(section: SettingSection.assetViewer)); + }, + icon: const Icon(Icons.settings), + ), + ], + ), + _getProgressBar(context), + ], + ), + ), + ), + ), + extendBody: true, + extendBodyBehindAppBar: true, + backgroundColor: Colors.black, + body: PhotoViewGestureDetectorScope( + axis: Axis.horizontal, + child: PageView.builder( + controller: _pageController, + physics: const FastClampingScrollPhysics(), + itemCount: widget.timeline.totalAssets, + onPageChanged: _pageChanged, + itemBuilder: (context, index) => Stack( + children: [ + if (_config.look == SlideshowLook.blurredBackground) _getBlur(context, index), + _getPhotoView(context, index), + ], + ), + ), + ), + ); + } +} diff --git a/mobile/lib/presentation/pages/drift_trash.page.dart b/mobile/lib/presentation/pages/drift_trash.page.dart index a85f69a75e..d21b437efe 100644 --- a/mobile/lib/presentation/pages/drift_trash.page.dart +++ b/mobile/lib/presentation/pages/drift_trash.page.dart @@ -1,13 +1,18 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/bottom_sheet/trash_bottom_sheet.widget.dart'; +import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; +import 'package:immich_mobile/widgets/common/confirm_dialog.dart'; +import 'package:immich_mobile/widgets/common/immich_toast.dart'; @RoutePage() class DriftTrashPage extends StatelessWidget { @@ -36,6 +41,7 @@ class DriftTrashPage extends StatelessWidget { pinned: true, centerTitle: true, elevation: 0, + actions: [const _TrashKebabMenu()], ), topSliverWidgetHeight: 24, topSliverWidget: Consumer( @@ -53,3 +59,89 @@ class DriftTrashPage extends StatelessWidget { ); } } + +class _TrashKebabMenu extends ConsumerWidget { + const _TrashKebabMenu(); + + Future _confirmAndRun( + BuildContext context, + WidgetRef ref, { + required String title, + required String content, + required Future Function(String userId) action, + required String Function(int count) successMsg, + }) async { + await showDialog( + context: context, + builder: (_) => ConfirmDialog( + title: title, + content: content, + onOk: () async { + final user = ref.read(currentUserProvider); + if (user == null) { + return; + } + final result = await action(user.id); + if (!context.mounted) { + return; + } + ImmichToast.show( + context: context, + msg: result.success ? successMsg(result.count) : context.t.scaffold_body_error_occurred, + toastType: result.success ? ToastType.success : ToastType.error, + ); + }, + ), + ); + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + return MenuAnchor( + consumeOutsideTap: true, + style: MenuStyle( + backgroundColor: WidgetStatePropertyAll(context.themeData.scaffoldBackgroundColor), + surfaceTintColor: const WidgetStatePropertyAll(Colors.grey), + elevation: const WidgetStatePropertyAll(4), + shape: const WidgetStatePropertyAll( + RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(12))), + ), + padding: const WidgetStatePropertyAll(EdgeInsets.symmetric(vertical: 6)), + ), + menuChildren: [ + BaseActionButton( + label: context.t.empty_trash, + iconData: Icons.delete_forever_outlined, + onPressed: () => _confirmAndRun( + context, + ref, + title: context.t.empty_trash, + content: context.t.empty_trash_confirmation, + action: ref.read(actionProvider.notifier).emptyTrash, + successMsg: (count) => context.t.assets_permanently_deleted_count(count: count), + ), + menuItem: true, + ), + BaseActionButton( + label: context.t.restore_all, + iconData: Icons.restore_outlined, + onPressed: () => _confirmAndRun( + context, + ref, + title: context.t.restore_all, + content: context.t.assets_restore_confirmation, + action: ref.read(actionProvider.notifier).restoreAllTrash, + successMsg: (count) => context.t.assets_restored_count(count: count), + ), + menuItem: true, + ), + ], + builder: (context, controller, child) { + return IconButton( + icon: const Icon(Icons.more_vert_rounded), + onPressed: () => controller.isOpen ? controller.close() : controller.open(), + ); + }, + ); + } +} diff --git a/mobile/lib/presentation/pages/edit/drift_edit.page.dart b/mobile/lib/presentation/pages/edit/drift_edit.page.dart index 8f7d874983..dcb340cc0e 100644 --- a/mobile/lib/presentation/pages/edit/drift_edit.page.dart +++ b/mobile/lib/presentation/pages/edit/drift_edit.page.dart @@ -95,7 +95,9 @@ class _DriftEditImagePageState extends ConsumerState with Ti return PopScope( canPop: !hasUnsavedEdits, onPopInvokedWithResult: (didPop, result) async { - if (didPop) return; + if (didPop) { + return; + } final shouldDiscard = await _showDiscardChangesDialog() ?? false; if (shouldDiscard && mounted) { Navigator.of(context).pop(); diff --git a/mobile/lib/presentation/pages/edit/editor.provider.dart b/mobile/lib/presentation/pages/edit/editor.provider.dart index 21b5268912..fcfb8be68e 100644 --- a/mobile/lib/presentation/pages/edit/editor.provider.dart +++ b/mobile/lib/presentation/pages/edit/editor.provider.dart @@ -179,7 +179,9 @@ class EditorState { @override bool operator ==(Object other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } return other is EditorState && other.isApplyingEdits == isApplyingEdits && diff --git a/mobile/lib/presentation/pages/profile/profile_picture_crop.page.dart b/mobile/lib/presentation/pages/profile/profile_picture_crop.page.dart index f460633cbb..3fb32b7d93 100644 --- a/mobile/lib/presentation/pages/profile/profile_picture_crop.page.dart +++ b/mobile/lib/presentation/pages/profile/profile_picture_crop.page.dart @@ -58,7 +58,9 @@ class _ProfilePictureCropPageState extends ConsumerState } Future _handleDone() async { - if (_isLoading) return; + if (_isLoading) { + return; + } setState(() { _isLoading = true; @@ -72,7 +74,9 @@ class _ProfilePictureCropPageState extends ConsumerState .read(uploadProfileImageProvider.notifier) .upload(xFile, fileName: 'profile-picture.png'); - if (!context.mounted) return; + if (!context.mounted) { + return; + } if (success) { final profileImagePath = ref.read(uploadProfileImageProvider).profileImagePath; @@ -102,7 +106,9 @@ class _ProfilePictureCropPageState extends ConsumerState ); } } catch (e) { - if (!context.mounted) return; + if (!context.mounted) { + return; + } ImmichToast.show( context: context, diff --git a/mobile/lib/presentation/pages/search/drift_search.page.dart b/mobile/lib/presentation/pages/search/drift_search.page.dart index 881daf9d38..2a40556641 100644 --- a/mobile/lib/presentation/pages/search/drift_search.page.dart +++ b/mobile/lib/presentation/pages/search/drift_search.page.dart @@ -13,6 +13,7 @@ import 'package:immich_mobile/domain/models/timeline.model.dart'; import 'package:immich_mobile/domain/services/timeline.service.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/generated/translations.g.dart'; import 'package:immich_mobile/models/search/search_filter.model.dart'; import 'package:immich_mobile/presentation/pages/search/paginated_search.provider.dart'; import 'package:immich_mobile/presentation/widgets/bottom_sheet/general_bottom_sheet.widget.dart'; @@ -106,10 +107,17 @@ class DriftSearchPage extends HookConsumerWidget { Future.microtask(() { textSearchController.clear(); + peopleCurrentFilterWidget.value = null; + dateRangeCurrentFilterWidget.value = null; + cameraCurrentFilterWidget.value = null; + tagCurrentFilterWidget.value = null; + mediaTypeCurrentFilterWidget.value = null; + ratingCurrentFilterWidget.value = null; + displayOptionCurrentFilterWidget.value = null; + locationCurrentFilterWidget.value = preFilter.location.city != null + ? Text(preFilter.location.city!, style: context.textTheme.labelLarge) + : null; search(preFilter); - if (preFilter.location.city != null) { - locationCurrentFilterWidget.value = Text(preFilter.location.city!, style: context.textTheme.labelLarge); - } }); return null; @@ -178,7 +186,7 @@ class DriftSearchPage extends HookConsumerWidget { expanded: true, onSearch: handleApply, onClear: handleClear, - child: TagPicker(onSelect: handleOnSelect, filter: (filter.value.tagIds ?? []).toSet()), + child: TagPicker(onSelectExistingTag: handleOnSelect, filter: (filter.value.tagIds ?? []).toSet()), ), ), ); @@ -701,7 +709,9 @@ class _SearchResultGrid extends ConsumerWidget { bool _onScrollUpdateNotification(ScrollNotification notification) { final metrics = notification.metrics; - if (metrics.axis != Axis.vertical) return false; + if (metrics.axis != Axis.vertical) { + return false; + } final isBottomSheet = notification.context?.findAncestorWidgetOfExactType() != null; final remaining = metrics.maxScrollExtent - metrics.pixels; @@ -728,7 +738,9 @@ class _SearchResultGrid extends ConsumerWidget { final hasMore = ref.watch(paginatedSearchProvider.select((s) => s.nextPage != null)); - if (hasMore) return null; + if (hasMore) { + return null; + } return SliverToBoxAdapter( child: Padding( @@ -868,6 +880,12 @@ class _QuickLinkList extends StatelessWidget { isTop: true, onTap: () => context.pushRoute(const DriftRecentlyTakenRoute()), ), + _QuickLink( + title: context.t.recently_added, + icon: Icons.upload_outlined, + isTop: true, + onTap: () => context.pushRoute(const DriftRecentlyAddedRoute()), + ), _QuickLink( title: 'videos'.t(context: context), icon: Icons.play_circle_outline_rounded, diff --git a/mobile/lib/presentation/pages/search/paginated_search.provider.dart b/mobile/lib/presentation/pages/search/paginated_search.provider.dart index f65ca6b909..fa2d5a06cf 100644 --- a/mobile/lib/presentation/pages/search/paginated_search.provider.dart +++ b/mobile/lib/presentation/pages/search/paginated_search.provider.dart @@ -44,7 +44,9 @@ class PaginatedSearchNotifier extends StateNotifier { Stream get assetCount => _assetCountController.stream; Future search(SearchFilter filter) async { - if (state.nextPage == null || state.isLoading) return; + if (state.nextPage == null || state.isLoading) { + return; + } state = SearchState(assets: state.assets, nextPage: state.nextPage, isLoading: true); diff --git a/mobile/lib/presentation/widgets/action_buttons/add_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/add_action_button.widget.dart index 39bdef8b9a..1ab3f2039d 100644 --- a/mobile/lib/presentation/widgets/action_buttons/add_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/add_action_button.widget.dart @@ -6,6 +6,7 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_bu import 'package:immich_mobile/presentation/widgets/action_buttons/unarchive_action_button.widget.dart'; import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'; import 'package:immich_mobile/presentation/widgets/album/album_selector.widget.dart'; +import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; import 'package:immich_mobile/providers/infrastructure/album.provider.dart'; import 'package:immich_mobile/providers/routes.provider.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; @@ -50,7 +51,9 @@ class _AddActionButtonState extends ConsumerState { List _buildMenuChildren() { final asset = ref.read(assetViewerProvider).currentAsset; - if (asset == null) return []; + if (asset == null) { + return []; + } final user = ref.read(currentUserProvider); final isOwner = asset is RemoteAsset && asset.ownerId == user?.id; @@ -140,13 +143,18 @@ class _AddActionButtonState extends ConsumerState { return; } - final addedCount = await ref.read(remoteAlbumProvider.notifier).addAssets(album.id, [latest.remoteId!]); + final result = await ref.read(actionProvider.notifier).addToAlbum(ActionSource.viewer, album); if (!context.mounted) { return; } - if (addedCount == 0) { + if (!result.success) { + ImmichToast.show(context: context, msg: 'scaffold_body_error_occurred'.tr(), toastType: ToastType.error); + return; + } + + if (result.count == 0) { ImmichToast.show( context: context, msg: 'add_to_album_bottom_sheet_already_exists'.tr(namedArgs: {'album': album.name}), @@ -157,7 +165,7 @@ class _AddActionButtonState extends ConsumerState { msg: 'add_to_album_bottom_sheet_added'.tr(namedArgs: {'album': album.name}), ); - // Invalidate using the asset's remote ID to refresh the "Appears in" list + // Refresh the "Appears in" list on the asset's info panel. ref.invalidate(albumsContainingAssetProvider(latest.remoteId!)); } diff --git a/mobile/lib/presentation/widgets/action_buttons/archive_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/archive_action_button.widget.dart index a673dff1d7..bb2cae21ad 100644 --- a/mobile/lib/presentation/widgets/action_buttons/archive_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/archive_action_button.widget.dart @@ -12,7 +12,9 @@ import 'package:immich_mobile/widgets/common/immich_toast.dart'; // used to allow performing archive action from different sources (without duplicating code) Future performArchiveAction(BuildContext context, WidgetRef ref, {required ActionSource source}) async { - if (!context.mounted) return; + if (!context.mounted) { + return; + } if (source == ActionSource.viewer) { EventStream.shared.emit(const ViewerReloadAssetEvent()); diff --git a/mobile/lib/presentation/widgets/action_buttons/base_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/base_action_button.widget.dart index 1ca875e483..5ed61c3bbe 100644 --- a/mobile/lib/presentation/widgets/action_buttons/base_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/base_action_button.widget.dart @@ -35,10 +35,11 @@ class BaseActionButton extends ConsumerWidget { final miniWidth = minWidth ?? (context.isMobile ? context.width / 4.5 : 75.0); final iconTheme = IconTheme.of(context); final iconSize = iconTheme.size ?? 24.0; - final iconColor = this.iconColor ?? iconTheme.color ?? context.themeData.iconTheme.color; final textColor = context.themeData.textTheme.labelLarge?.color; if (iconOnly) { + final iconColor = this.iconColor ?? iconTheme.color ?? context.themeData.iconTheme.color; + return IconButton( onPressed: onPressed, icon: Icon(iconData, size: iconSize, color: iconColor), @@ -46,17 +47,21 @@ class BaseActionButton extends ConsumerWidget { } if (menuItem) { - final theme = context.themeData; - final effectiveIconColor = iconColor ?? theme.iconTheme.color ?? theme.colorScheme.onSurfaceVariant; + final iconColor = this.iconColor; return MenuItemButton( - style: MenuItemButton.styleFrom(alignment: Alignment.centerLeft, padding: const EdgeInsets.all(16)), - leadingIcon: Icon(iconData, color: effectiveIconColor), + style: MenuItemButton.styleFrom( + alignment: Alignment.centerLeft, + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), + ), + leadingIcon: Icon(iconData, color: iconColor, size: 20), onPressed: onPressed, - child: Text(label, style: theme.textTheme.labelLarge?.copyWith(fontSize: 16, color: iconColor)), + child: Text(label, style: TextStyle(fontSize: 15, color: iconColor)), ); } + final iconColor = this.iconColor ?? iconTheme.color ?? context.themeData.iconTheme.color; + return ConstrainedBox( constraints: BoxConstraints(maxWidth: maxWidth), child: MaterialButton( diff --git a/mobile/lib/presentation/widgets/action_buttons/bulk_tag_assets_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/bulk_tag_assets_action_button.widget.dart new file mode 100644 index 0000000000..b9ac47cd57 --- /dev/null +++ b/mobile/lib/presentation/widgets/action_buttons/bulk_tag_assets_action_button.widget.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; +import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; +import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; +import 'package:immich_mobile/widgets/common/immich_toast.dart'; + +class BulkTagAssetsActionButton extends ConsumerWidget { + final ActionSource source; + + const BulkTagAssetsActionButton({super.key, required this.source}); + + Future _onTap(BuildContext context, WidgetRef ref) async { + final result = await ref.read(actionProvider.notifier).tagAssets(source, context); + if (result == null) { + return; + } + + ref.read(multiSelectProvider.notifier).reset(); + + if (!context.mounted) { + return; + } + + ImmichToast.show( + context: context, + msg: result.success + ? 'tagged_assets'.t(context: context, args: {'count': result.count.toString()}) + : 'errors.failed_to_tag_assets'.t(context: context), + gravity: ToastGravity.BOTTOM, + toastType: result.success ? ToastType.success : ToastType.error, + ); + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + return BaseActionButton( + iconData: Icons.sell_outlined, + label: "control_bottom_app_bar_add_tags".t(context: context), + onPressed: () => _onTap(context, ref), + ); + } +} diff --git a/mobile/lib/presentation/widgets/action_buttons/delete_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/delete_action_button.widget.dart index 2121ef3159..92747c6d44 100644 --- a/mobile/lib/presentation/widgets/action_buttons/delete_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/delete_action_button.widget.dart @@ -54,7 +54,9 @@ class DeleteActionButton extends ConsumerWidget { ], ), ); - if (confirm != true) return; + if (confirm != true) { + return; + } } if (source == ActionSource.viewer) { diff --git a/mobile/lib/presentation/widgets/action_buttons/delete_local_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/delete_local_action_button.widget.dart index 2e15de49df..6911d09f89 100644 --- a/mobile/lib/presentation/widgets/action_buttons/delete_local_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/delete_local_action_button.widget.dart @@ -9,6 +9,7 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_bu import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; +import 'package:immich_mobile/providers/infrastructure/album.provider.dart'; /// This delete action has the following behavior: /// - Prompt to delete the asset locally @@ -39,6 +40,8 @@ class DeleteLocalActionButton extends ConsumerWidget { return; } + ref.invalidate(localAlbumProvider); + final successMessage = 'delete_local_action_prompt'.t(context: context, args: {'count': result.count.toString()}); if (context.mounted) { diff --git a/mobile/lib/presentation/widgets/action_buttons/delete_permanent_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/delete_permanent_action_button.widget.dart index 27a1a4d8af..267a9f55e6 100644 --- a/mobile/lib/presentation/widgets/action_buttons/delete_permanent_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/delete_permanent_action_button.widget.dart @@ -18,8 +18,15 @@ class DeletePermanentActionButton extends ConsumerWidget { final ActionSource source; final bool iconOnly; final bool menuItem; + final bool useShortLabel; - const DeletePermanentActionButton({super.key, required this.source, this.iconOnly = false, this.menuItem = false}); + const DeletePermanentActionButton({ + super.key, + required this.source, + this.iconOnly = false, + this.menuItem = false, + this.useShortLabel = false, + }); void _onTap(BuildContext context, WidgetRef ref) async { if (!context.mounted) { @@ -33,7 +40,9 @@ class DeletePermanentActionButton extends ConsumerWidget { builder: (context) => PermanentDeleteDialog(count: count), ) ?? false; - if (!confirm) return; + if (!confirm) { + return; + } if (source == ActionSource.viewer) { EventStream.shared.emit(const ViewerReloadAssetEvent()); @@ -62,7 +71,7 @@ class DeletePermanentActionButton extends ConsumerWidget { return BaseActionButton( maxWidth: 110.0, iconData: Icons.delete_forever, - label: "delete_permanently".t(context: context), + label: useShortLabel ? "delete".t(context: context) : "delete_permanently".t(context: context), iconOnly: iconOnly, menuItem: menuItem, onPressed: () => _onTap(context, ref), diff --git a/mobile/lib/presentation/widgets/action_buttons/move_to_lock_folder_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/move_to_lock_folder_action_button.widget.dart index 2f7c3899eb..56191e9055 100644 --- a/mobile/lib/presentation/widgets/action_buttons/move_to_lock_folder_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/move_to_lock_folder_action_button.widget.dart @@ -12,7 +12,9 @@ import 'package:immich_mobile/widgets/common/immich_toast.dart'; // Reusable helper: move to locked folder from any source (e.g called from menu) Future performMoveToLockFolderAction(BuildContext context, WidgetRef ref, {required ActionSource source}) async { - if (!context.mounted) return; + if (!context.mounted) { + return; + } if (source == ActionSource.viewer) { EventStream.shared.emit(const ViewerReloadAssetEvent()); diff --git a/mobile/lib/presentation/widgets/action_buttons/open_in_browser_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/open_in_browser_action_button.widget.dart index 17703d0beb..541a9f8093 100644 --- a/mobile/lib/presentation/widgets/action_buttons/open_in_browser_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/open_in_browser_action_button.widget.dart @@ -12,7 +12,6 @@ class OpenInBrowserActionButton extends ConsumerWidget { final TimelineOrigin origin; final bool iconOnly; final bool menuItem; - final Color? iconColor; const OpenInBrowserActionButton({ super.key, @@ -20,7 +19,6 @@ class OpenInBrowserActionButton extends ConsumerWidget { required this.origin, this.iconOnly = false, this.menuItem = false, - this.iconColor, }); void _onTap() async { @@ -52,7 +50,6 @@ class OpenInBrowserActionButton extends ConsumerWidget { return BaseActionButton( label: 'open_in_browser'.t(context: context), iconData: Icons.open_in_browser, - iconColor: iconColor, iconOnly: iconOnly, menuItem: menuItem, onPressed: _onTap, diff --git a/mobile/lib/presentation/widgets/action_buttons/restore_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/restore_action_button.widget.dart new file mode 100644 index 0000000000..1713718967 --- /dev/null +++ b/mobile/lib/presentation/widgets/action_buttons/restore_action_button.widget.dart @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/domain/models/events.model.dart'; +import 'package:immich_mobile/domain/utils/event_stream.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; +import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; +import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; +import 'package:immich_mobile/widgets/common/immich_toast.dart'; + +class RestoreActionButton extends ConsumerWidget { + final ActionSource source; + final bool iconOnly; + final bool menuItem; + + const RestoreActionButton({super.key, required this.source, this.iconOnly = false, this.menuItem = false}); + + void _onTap(BuildContext context, WidgetRef ref) async { + if (!context.mounted) { + return; + } + + final result = await ref.read(actionProvider.notifier).restoreTrash(source); + ref.read(multiSelectProvider.notifier).reset(); + + if (source == ActionSource.viewer) { + EventStream.shared.emit(const ViewerReloadAssetEvent()); + } + + final successMessage = 'assets_restored_count'.t(context: context, args: {'count': result.count.toString()}); + + if (context.mounted) { + ImmichToast.show( + context: context, + msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), + gravity: ToastGravity.BOTTOM, + toastType: result.success ? ToastType.success : ToastType.error, + ); + } + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + return BaseActionButton( + iconData: Icons.history_rounded, + label: 'restore'.t(context: context), + iconOnly: iconOnly, + menuItem: menuItem, + onPressed: () => _onTap(context, ref), + maxWidth: 100.0, + ); + } +} diff --git a/mobile/lib/presentation/widgets/action_buttons/share_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/share_action_button.widget.dart index 6fbd6f7dfa..7bc5dacb16 100644 --- a/mobile/lib/presentation/widgets/action_buttons/share_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/share_action_button.widget.dart @@ -14,7 +14,9 @@ import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; class _SharePreparingDialog extends StatelessWidget { - const _SharePreparingDialog(); + final ValueNotifier progress; + + const _SharePreparingDialog({required this.progress}); @override Widget build(BuildContext context) { @@ -22,8 +24,24 @@ class _SharePreparingDialog extends StatelessWidget { content: Column( mainAxisSize: MainAxisSize.min, children: [ - const CircularProgressIndicator(), - Container(margin: const EdgeInsets.only(top: 12), child: const Text('share_dialog_preparing').tr()), + Container(margin: const EdgeInsets.only(bottom: 12), child: const Text('share_dialog_preparing').tr()), + SizedBox( + width: 240, + child: ValueListenableBuilder( + valueListenable: progress, + builder: (context, value, _) { + final percent = value == null ? null : (value * 100).clamp(0, 100); + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + LinearProgressIndicator(value: value, minHeight: 8.0), + if (percent != null) + Container(margin: const EdgeInsets.only(top: 8), child: Text('${percent.toStringAsFixed(0)}%')), + ], + ); + }, + ), + ), ], ), ); @@ -43,32 +61,39 @@ class ShareActionButton extends ConsumerWidget { } final cancelCompleter = Completer(); - const preparingDialog = _SharePreparingDialog(); + final progress = ValueNotifier(null); + final preparingDialog = _SharePreparingDialog(progress: progress); await showDialog( context: context, builder: (BuildContext buildContext) { - ref.read(actionProvider.notifier).shareAssets(source, context, cancelCompleter: cancelCompleter).then(( - ActionResult result, - ) { - if (cancelCompleter.isCompleted || !context.mounted) { - return; - } + ref + .read(actionProvider.notifier) + .shareAssets( + source, + context, + cancelCompleter: cancelCompleter, + onAssetDownloadProgress: (value) => progress.value = value, + ) + .then((ActionResult result) { + if (cancelCompleter.isCompleted || !context.mounted) { + return; + } - ref.read(multiSelectProvider.notifier).reset(); + ref.read(multiSelectProvider.notifier).reset(); - if (!result.success) { - ImmichToast.show( - context: context, - msg: 'scaffold_body_error_occurred'.t(context: context), - gravity: ToastGravity.BOTTOM, - toastType: ToastType.error, - ); - } + if (!result.success) { + ImmichToast.show( + context: context, + msg: 'scaffold_body_error_occurred'.t(context: context), + gravity: ToastGravity.BOTTOM, + toastType: ToastType.error, + ); + } - buildContext.pop(); - }); + buildContext.pop(); + }); - // show a loading spinner with a "Preparing" message + // Show download progress with a "Preparing" message return preparingDialog; }, barrierDismissible: false, @@ -77,6 +102,7 @@ class ShareActionButton extends ConsumerWidget { if (!cancelCompleter.isCompleted) { cancelCompleter.complete(); } + progress.dispose(); }); } diff --git a/mobile/lib/presentation/widgets/action_buttons/similar_photos_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/similar_photos_action_button.widget.dart index 0acbbce613..42dcfa683a 100644 --- a/mobile/lib/presentation/widgets/action_buttons/similar_photos_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/similar_photos_action_button.widget.dart @@ -37,7 +37,7 @@ class SimilarPhotosActionButton extends ConsumerWidget { date: SearchDateFilter(), display: SearchDisplayFilters(isNotInAlbum: false, isArchive: false, isFavorite: false), rating: SearchRatingFilter(), - mediaType: AssetType.image, + mediaType: AssetType.other, ), ); diff --git a/mobile/lib/presentation/widgets/action_buttons/slideshow_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/slideshow_action_button.widget.dart new file mode 100644 index 0000000000..479cf2dfe9 --- /dev/null +++ b/mobile/lib/presentation/widgets/action_buttons/slideshow_action_button.widget.dart @@ -0,0 +1,34 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; +import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; +import 'package:immich_mobile/routing/router.dart'; + +class SlideshowActionButton extends ConsumerWidget { + final bool iconOnly; + final bool menuItem; + + const SlideshowActionButton({super.key, this.iconOnly = false, this.menuItem = false}); + + void _onTap(BuildContext context, WidgetRef ref) { + if (!context.mounted) { + return; + } + + context.pushRoute(DriftSlideshowRoute(timeline: ref.read(timelineServiceProvider))); + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + return BaseActionButton( + iconData: Icons.slideshow, + label: "slideshow".t(context: context), + iconOnly: iconOnly, + menuItem: menuItem, + onPressed: () => _onTap(context, ref), + maxWidth: 100, + ); + } +} diff --git a/mobile/lib/presentation/widgets/action_buttons/unarchive_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/unarchive_action_button.widget.dart index 98e868d953..57221303a8 100644 --- a/mobile/lib/presentation/widgets/action_buttons/unarchive_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/unarchive_action_button.widget.dart @@ -14,7 +14,9 @@ import 'package:immich_mobile/domain/utils/event_stream.dart'; // used to allow performing unarchive action from different sources (without duplicating code) Future performUnArchiveAction(BuildContext context, WidgetRef ref, {required ActionSource source}) async { - if (!context.mounted) return; + if (!context.mounted) { + return; + } if (source == ActionSource.viewer) { EventStream.shared.emit(const ViewerReloadAssetEvent()); diff --git a/mobile/lib/presentation/widgets/action_buttons/upload_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/upload_action_button.widget.dart index 98eb09a4aa..599e11d467 100644 --- a/mobile/lib/presentation/widgets/action_buttons/upload_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/upload_action_button.widget.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:io'; import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; @@ -10,6 +11,9 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_bu import 'package:immich_mobile/providers/backup/asset_upload_progress.provider.dart'; import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; +import 'package:immich_mobile/providers/view_intent/view_intent_file_path.provider.dart'; +import 'package:immich_mobile/services/foreground_upload.service.dart'; +import 'package:immich_mobile/services/view_intent.service.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; import 'package:immich_ui/immich_ui.dart'; @@ -26,7 +30,11 @@ class UploadActionButton extends ConsumerWidget { } final isTimeline = source == ActionSource.timeline; + final viewerIntentFilePath = source == ActionSource.viewer ? ref.read(viewIntentFilePathProvider) : null; List? assets; + var isUploadDialogOpen = false; + var wasUploadCancelled = false; + Future? uploadDialogFuture; if (source == ActionSource.timeline) { assets = ref.read(multiSelectProvider).selectedAssets.whereType().toList(); @@ -35,22 +43,50 @@ class UploadActionButton extends ConsumerWidget { } ref.read(multiSelectProvider.notifier).reset(); } else { - unawaited( - showDialog( - context: context, - barrierDismissible: false, - builder: (dialogContext) => const _UploadProgressDialog(), - ), - ); + isUploadDialogOpen = true; + uploadDialogFuture = + showDialog( + context: context, + barrierDismissible: false, + builder: (dialogContext) => _UploadProgressDialog( + onCancel: () { + wasUploadCancelled = true; + }, + ), + ).whenComplete(() { + isUploadDialogOpen = false; + }); + unawaited(uploadDialogFuture); } - final result = await ref.read(actionProvider.notifier).upload(source, assets: assets); + var success = false; + if (!isTimeline && viewerIntentFilePath != null) { + final viewIntentService = ref.read(viewIntentServiceProvider); + viewIntentService.markUploadActive(viewerIntentFilePath); + var hasError = false; + try { + await ref + .read(foregroundUploadServiceProvider) + .uploadShareIntent( + [File(viewerIntentFilePath)], + onError: (_, _) { + hasError = true; + }, + ); + } finally { + await viewIntentService.markUploadInactive(viewerIntentFilePath); + } + success = !hasError; + } else { + final result = await ref.read(actionProvider.notifier).upload(source, assets: assets); + success = result.success; + } - if (!isTimeline && context.mounted) { + if (!isTimeline && context.mounted && isUploadDialogOpen) { Navigator.of(context, rootNavigator: true).pop(); } - if (context.mounted && !result.success) { + if (context.mounted && !success && !wasUploadCancelled) { ImmichToast.show( context: context, msg: 'scaffold_body_error_occurred'.t(context: context), @@ -73,7 +109,9 @@ class UploadActionButton extends ConsumerWidget { } class _UploadProgressDialog extends ConsumerWidget { - const _UploadProgressDialog(); + final VoidCallback onCancel; + + const _UploadProgressDialog({required this.onCancel}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -103,7 +141,8 @@ class _UploadProgressDialog extends ConsumerWidget { onPressed: () { ref.read(manualUploadCancelTokenProvider)?.complete(); ref.read(manualUploadCancelTokenProvider.notifier).state = null; - Navigator.of(context).pop(); + onCancel(); + Navigator.of(context, rootNavigator: true).pop(); }, labelText: 'cancel'.t(context: context), ), diff --git a/mobile/lib/presentation/widgets/album/album_selector.widget.dart b/mobile/lib/presentation/widgets/album/album_selector.widget.dart index c68a7273e0..5a174bfc5e 100644 --- a/mobile/lib/presentation/widgets/album/album_selector.widget.dart +++ b/mobile/lib/presentation/widgets/album/album_selector.widget.dart @@ -7,7 +7,6 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/domain/models/album/album.model.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/theme_extensions.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; @@ -16,14 +15,13 @@ import 'package:immich_mobile/presentation/widgets/album/album_tile.dart'; import 'package:immich_mobile/presentation/widgets/album/new_album_name_modal.widget.dart'; import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart'; import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart'; -import 'package:immich_mobile/providers/app_settings.provider.dart'; import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'; import 'package:immich_mobile/providers/infrastructure/album.provider.dart'; import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; import 'package:immich_mobile/routing/router.dart'; -import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/utils/album_filter.utils.dart'; import 'package:immich_mobile/widgets/common/confirm_dialog.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; @@ -58,19 +56,11 @@ class _AlbumSelectorState extends ConsumerState { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { - final appSettings = ref.read(appSettingsServiceProvider); - final savedSortMode = appSettings.getSetting(AppSettingsEnum.selectedAlbumSortOrder); - final savedIsReverse = appSettings.getSetting(AppSettingsEnum.selectedAlbumSortReverse); - final savedIsGrid = appSettings.getSetting(AppSettingsEnum.albumGridView); - - final albumSortMode = AlbumSortMode.values.firstWhere( - (e) => e.storeIndex == savedSortMode, - orElse: () => AlbumSortMode.lastModified, - ); + final albumConfig = ref.read(appConfigProvider).album; setState(() { - sort = AlbumSort(mode: albumSortMode, isReverse: savedIsReverse); - isGrid = savedIsGrid; + sort = AlbumSort(mode: albumConfig.sortMode, isReverse: albumConfig.isReverse); + isGrid = albumConfig.isGrid; }); ref.read(remoteAlbumProvider.notifier).refresh(); @@ -102,7 +92,7 @@ class _AlbumSelectorState extends ConsumerState { setState(() { isGrid = !isGrid; }); - ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.albumGridView, isGrid); + ref.read(settingsProvider).write(.albumIsGrid, isGrid); } void changeFilter(QuickFilterMode mode) { @@ -118,9 +108,9 @@ class _AlbumSelectorState extends ConsumerState { this.sort = sort; }); - final appSettings = ref.read(appSettingsServiceProvider); - await appSettings.setSetting(AppSettingsEnum.selectedAlbumSortOrder, sort.mode.storeIndex); - await appSettings.setSetting(AppSettingsEnum.selectedAlbumSortReverse, sort.isReverse); + final metadata = ref.read(settingsProvider); + await metadata.write(.albumSortMode, sort.mode); + await metadata.write(.albumIsReverse, sort.isReverse); await sortAlbums(); } @@ -755,12 +745,10 @@ class AddToAlbumHeader extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { Future onCreateAlbum() async { + final selectedAssets = ref.read(multiSelectProvider).selectedAssets; final newAlbum = await ref .read(remoteAlbumProvider.notifier) - .createAlbum( - title: "Untitled Album", - assetIds: ref.read(multiSelectProvider).selectedAssets.map((e) => (e as RemoteAsset).id).toList(), - ); + .createAlbumWithAssets(title: "Untitled Album", assets: selectedAssets); if (newAlbum == null) { ImmichToast.show(context: context, toastType: ToastType.error, msg: 'errors.failed_to_create_album'.tr()); diff --git a/mobile/lib/presentation/widgets/album/pending_uploads_banner.widget.dart b/mobile/lib/presentation/widgets/album/pending_uploads_banner.widget.dart new file mode 100644 index 0000000000..2701316e75 --- /dev/null +++ b/mobile/lib/presentation/widgets/album/pending_uploads_banner.widget.dart @@ -0,0 +1,269 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart'; +import 'package:immich_mobile/providers/album/pending_album_uploads.provider.dart'; +import 'package:immich_mobile/providers/backup/asset_upload_progress.provider.dart'; + +/// Pinned banner sliver that surfaces in-flight album uploads directly under +/// the album app bar. Renders nothing while the queue is empty. Tapping the +/// banner opens a bottom sheet with per-asset progress. +class PendingUploadsBanner extends ConsumerWidget { + static const double _height = 52; + + final String albumId; + + const PendingUploadsBanner({super.key, required this.albumId}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final pending = ref.watch(pendingAlbumUploadsProvider(albumId)); + if (pending.isEmpty) { + return const SliverToBoxAdapter(child: SizedBox.shrink()); + } + + final hasFailures = pending.any((p) => p.failed); + final clamped = pending.map((p) => p.progress.clamp(0.0, 1.0)).toList(growable: false); + final overallProgress = clamped.isEmpty ? 0.0 : clamped.reduce((a, b) => a + b) / clamped.length; + final isIndeterminate = overallProgress <= 0.0; + + return SliverPersistentHeader( + pinned: true, + delegate: _PendingUploadsBannerDelegate( + height: _height, + child: _PendingUploadsBannerContent( + albumId: albumId, + previewAsset: pending.first.asset, + count: pending.length, + overallProgress: overallProgress, + isIndeterminate: isIndeterminate, + hasFailures: hasFailures, + ), + ), + ); + } + + static void _openSheet(BuildContext context, String albumId) { + showModalBottomSheet( + context: context, + showDragHandle: true, + builder: (_) => _PendingUploadsSheet(albumId: albumId), + ); + } +} + +class _PendingUploadsBannerDelegate extends SliverPersistentHeaderDelegate { + final double height; + final Widget child; + + const _PendingUploadsBannerDelegate({required this.height, required this.child}); + + @override + double get minExtent => height; + + @override + double get maxExtent => height; + + @override + Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) => child; + + @override + bool shouldRebuild(covariant _PendingUploadsBannerDelegate oldDelegate) => + height != oldDelegate.height || child != oldDelegate.child; +} + +class _PendingUploadsBannerContent extends StatelessWidget { + final String albumId; + final BaseAsset previewAsset; + final int count; + final double overallProgress; + final bool isIndeterminate; + final bool hasFailures; + + const _PendingUploadsBannerContent({ + required this.albumId, + required this.previewAsset, + required this.count, + required this.overallProgress, + required this.isIndeterminate, + required this.hasFailures, + }); + + @override + Widget build(BuildContext context) { + final percentLabel = isIndeterminate ? '' : ' ยท ${(overallProgress * 100).toInt()}%'; + return Material( + color: hasFailures ? context.colorScheme.errorContainer : context.colorScheme.surfaceContainerHigh, + child: InkWell( + onTap: () => PendingUploadsBanner._openSheet(context, albumId), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Row( + children: [ + ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(4)), + child: SizedBox(width: 32, height: 32, child: Thumbnail.fromAsset(asset: previewAsset)), + ), + const SizedBox(width: 12), + Expanded( + child: Text( + '${'uploading'.t(context: context)} $count$percentLabel', + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: context.textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.w500), + ), + ), + if (hasFailures) + Padding( + padding: const EdgeInsets.only(left: 8), + child: Icon(Icons.error_outline, color: context.colorScheme.error, size: 20), + ), + Icon(Icons.chevron_right_rounded, color: context.colorScheme.onSurfaceVariant), + ], + ), + ), + ), + SizedBox( + height: 3, + child: LinearProgressIndicator( + value: isIndeterminate ? null : overallProgress, + backgroundColor: context.colorScheme.surfaceContainerHighest, + valueColor: AlwaysStoppedAnimation( + hasFailures ? context.colorScheme.error : context.colorScheme.primary, + ), + ), + ), + ], + ), + ), + ); + } +} + +class _PendingUploadsSheet extends ConsumerWidget { + final String albumId; + + const _PendingUploadsSheet({required this.albumId}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final pending = ref.watch(pendingAlbumUploadsProvider(albumId)); + + // Auto-dismiss when the queue empties. + if (pending.isEmpty) { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (Navigator.of(context).canPop()) { + Navigator.of(context).pop(); + } + }); + return const SizedBox.shrink(); + } + + final failedCount = pending.where((p) => p.failed).length; + final inFlightCount = pending.length - failedCount; + final canAbort = inFlightCount > 0 && ref.watch(manualUploadCancelTokenProvider) != null; + + return SafeArea( + child: Padding( + padding: const EdgeInsets.fromLTRB(16, 0, 16, 16), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(bottom: 12), + child: Row( + children: [ + Expanded( + child: Text( + '${'uploading'.t(context: context)} (${pending.length})', + style: context.textTheme.titleMedium, + ), + ), + if (canAbort) + TextButton.icon( + onPressed: () { + final cancelToken = ref.read(manualUploadCancelTokenProvider); + if (cancelToken != null && !cancelToken.isCompleted) { + cancelToken.complete(); + } + ref.read(manualUploadCancelTokenProvider.notifier).state = null; + ref.read(pendingAlbumUploadsProvider(albumId).notifier).clear(); + }, + icon: const Icon(Icons.stop_circle_outlined, size: 18), + label: Text('cancel'.t(context: context)), + style: TextButton.styleFrom(foregroundColor: context.colorScheme.error), + ) + else if (failedCount > 0) + TextButton.icon( + onPressed: () => ref.read(pendingAlbumUploadsProvider(albumId).notifier).clearFailed(), + icon: const Icon(Icons.clear_rounded, size: 18), + label: Text('clear_failed_count'.t(context: context, args: {'count': failedCount})), + style: TextButton.styleFrom(foregroundColor: context.colorScheme.error), + ), + ], + ), + ), + SizedBox( + height: 96, + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: pending.length, + separatorBuilder: (_, __) => const SizedBox(width: 8), + itemBuilder: (_, index) => _PendingUploadTile(entry: pending[index]), + ), + ), + ], + ), + ), + ); + } +} + +class _PendingUploadTile extends StatelessWidget { + final PendingAlbumUpload entry; + + const _PendingUploadTile({required this.entry}); + + @override + Widget build(BuildContext context) { + return ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(8)), + child: SizedBox( + width: 96, + height: 96, + child: Stack( + fit: StackFit.expand, + children: [ + Thumbnail.fromAsset(asset: entry.asset), + Positioned.fill( + child: ColoredBox( + color: entry.failed ? Colors.red.withValues(alpha: 0.6) : Colors.black54, + child: Center( + child: entry.failed + ? const Icon(Icons.error_outline, color: Colors.white, size: 28) + : SizedBox( + width: 32, + height: 32, + child: CircularProgressIndicator( + value: entry.progress > 0 ? entry.progress : null, + strokeWidth: 2.5, + backgroundColor: Colors.white24, + valueColor: const AlwaysStoppedAnimation(Colors.white), + ), + ), + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/mobile/lib/presentation/widgets/asset_viewer/asset_details.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/asset_details.widget.dart index dd5743a2d0..4b59e1ae60 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/asset_details.widget.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/asset_details.widget.dart @@ -26,6 +26,14 @@ class AssetDetails extends ConsumerWidget { decoration: BoxDecoration( color: context.colorScheme.surface, borderRadius: const BorderRadius.vertical(top: Radius.circular(20)), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.2), + offset: const Offset(0, -3), + blurRadius: 10, + spreadRadius: 2, + ), + ], ), child: SafeArea( top: false, diff --git a/mobile/lib/presentation/widgets/asset_viewer/asset_details/appears_in_details.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/asset_details/appears_in_details.widget.dart index fc15503a3f..6a565fa2cd 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/asset_details/appears_in_details.widget.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/asset_details/appears_in_details.widget.dart @@ -21,21 +21,27 @@ class AppearsInDetails extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - if (!asset.hasRemote) return const SizedBox.shrink(); + if (!asset.hasRemote) { + return const SizedBox.shrink(); + } final remoteAssetId = switch (asset) { RemoteAsset(:final id) => id, LocalAsset(:final remoteAssetId) => remoteAssetId, }; - if (remoteAssetId == null) return const SizedBox.shrink(); + if (remoteAssetId == null) { + return const SizedBox.shrink(); + } final userId = ref.watch(currentUserProvider)?.id; final assetAlbums = ref.watch(albumsContainingAssetProvider(remoteAssetId)); return assetAlbums.when( data: (albums) { - if (albums.isEmpty) return const SizedBox.shrink(); + if (albums.isEmpty) { + return const SizedBox.shrink(); + } albums.sortBy((a) => a.name); diff --git a/mobile/lib/presentation/widgets/asset_viewer/asset_details/rating_details.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/asset_details/rating_details.widget.dart index fb3a9dd8a8..1056626119 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/asset_details/rating_details.widget.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/asset_details/rating_details.widget.dart @@ -20,7 +20,9 @@ class RatingDetails extends ConsumerWidget { .watch(userMetadataPreferencesProvider) .maybeWhen(data: (prefs) => prefs?.ratingsEnabled ?? false, orElse: () => false); - if (!isRatingEnabled) return const SizedBox.shrink(); + if (!isRatingEnabled) { + return const SizedBox.shrink(); + } return Padding( padding: const EdgeInsets.only(left: 16.0, top: 16.0), diff --git a/mobile/lib/presentation/widgets/asset_viewer/asset_details/technical_details.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/asset_details/technical_details.widget.dart index 52d00828f1..33e0fa38f5 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/asset_details/technical_details.widget.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/asset_details/technical_details.widget.dart @@ -22,6 +22,7 @@ class TechnicalDetails extends ConsumerWidget { final exifInfo = this.exifInfo; final cameraTitle = _getCameraInfoTitle(exifInfo); final lensTitle = exifInfo?.lens != null && exifInfo!.lens!.isNotEmpty ? exifInfo.lens : null; + final lensSubtitle = _getLensInfoSubtitle(exifInfo); return Column( children: [ @@ -46,9 +47,16 @@ class TechnicalDetails extends ConsumerWidget { title: lensTitle, titleStyle: context.textTheme.labelLarge, leading: Icon(Icons.camera_outlined, size: 24, color: context.textTheme.labelLarge?.color), - subtitle: _getLensInfoSubtitle(exifInfo), + subtitle: lensSubtitle, subtitleStyle: context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.onSurfaceSecondary), ), + ] else if (lensSubtitle != null) ...[ + const SizedBox(height: 16), + SheetTile( + title: lensSubtitle, + titleStyle: context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.onSurfaceSecondary), + leading: Icon(Icons.camera_outlined, size: 24, color: context.textTheme.labelLarge?.color), + ), ], ], ); @@ -103,7 +111,9 @@ class TechnicalDetails extends ConsumerWidget { } static String? _getCameraInfoTitle(ExifInfo? exifInfo) { - if (exifInfo == null) return null; + if (exifInfo == null) { + return null; + } return switch ((exifInfo.make, exifInfo.model)) { (null, null) => null, (String make, null) => make, @@ -113,16 +123,23 @@ class TechnicalDetails extends ConsumerWidget { } static String? _getCameraInfoSubtitle(ExifInfo? exifInfo) { - if (exifInfo == null) return null; + if (exifInfo == null) { + return null; + } final exposureTime = exifInfo.exposureTime.isNotEmpty ? exifInfo.exposureTime : null; final iso = exifInfo.iso != null ? 'ISO ${exifInfo.iso}' : null; return [exposureTime, iso].where((spec) => spec != null && spec.isNotEmpty).join(_kSeparator); } static String? _getLensInfoSubtitle(ExifInfo? exifInfo) { - if (exifInfo == null) return null; + if (exifInfo == null) { + return null; + } final fNumber = exifInfo.fNumber.isNotEmpty ? 'ฦ’/${exifInfo.fNumber}' : null; final focalLength = exifInfo.focalLength.isNotEmpty ? '${exifInfo.focalLength} mm' : null; + if (fNumber == null && focalLength == null) { + return null; + } return [fNumber, focalLength].where((spec) => spec != null && spec.isNotEmpty).join(_kSeparator); } } diff --git a/mobile/lib/presentation/widgets/asset_viewer/asset_page.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/asset_page.widget.dart index cf8be58219..5e60d754fd 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/asset_page.widget.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/asset_page.widget.dart @@ -15,14 +15,14 @@ import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_details.wi import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_stack.provider.dart'; import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_stack.widget.dart'; import 'package:immich_mobile/presentation/widgets/asset_viewer/ocr_overlay.widget.dart'; -import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'; import 'package:immich_mobile/presentation/widgets/asset_viewer/video_viewer.widget.dart'; import 'package:immich_mobile/presentation/widgets/images/image_provider.dart'; import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart'; -import 'package:immich_mobile/providers/app_settings.provider.dart'; +import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'; import 'package:immich_mobile/providers/asset_viewer/is_motion_video_playing.provider.dart'; -import 'package:immich_mobile/services/app_settings.service.dart'; +import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; +import 'package:immich_mobile/providers/view_intent/view_intent_file_path.provider.dart'; import 'package:immich_mobile/widgets/common/immich_loading_indicator.dart'; import 'package:immich_mobile/widgets/photo_view/photo_view.dart'; @@ -58,12 +58,17 @@ class _AssetPageState extends ConsumerState { _DragIntent _dragIntent = _DragIntent.none; Drag? _drag; + BaseAsset? _asset; + @override void initState() { super.initState(); _eventSubscription = EventStream.shared.listen(_onEvent); + _asset = ref.read(timelineServiceProvider).getAssetSafe(widget.index); WidgetsBinding.instance.addPostFrameCallback((_) { - if (!mounted || !_scrollController.hasClients) return; + if (!mounted || !_scrollController.hasClients) { + return; + } _scrollController.snapPosition.snapOffset = _snapOffset; if (_showingDetails && _snapOffset > 0) { _scrollController.jumpTo(_snapOffset); @@ -71,6 +76,14 @@ class _AssetPageState extends ConsumerState { }); } + @override + void didUpdateWidget(AssetPage oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.index != widget.index) { + _asset = ref.read(timelineServiceProvider).getAssetSafe(widget.index); + } + } + @override void dispose() { _scrollController.dispose(); @@ -88,7 +101,9 @@ class _AssetPageState extends ConsumerState { } void _showDetails() { - if (!_scrollController.hasClients || _snapOffset <= 0) return; + if (!_scrollController.hasClients || _snapOffset <= 0) { + return; + } _viewer.setShowingDetails(true); _scrollController.animateTo(_snapOffset, duration: Durations.medium2, curve: Curves.easeOutCubic); } @@ -129,7 +144,9 @@ class _AssetPageState extends ConsumerState { } void _updateDrag(DragUpdateDetails details) { - if (_dragStart == null) return; + if (_dragStart == null) { + return; + } if (_dragIntent == _DragIntent.none) { _dragIntent = switch ((details.globalPosition - _dragStart!.globalPosition).dy) { @@ -142,7 +159,9 @@ class _AssetPageState extends ConsumerState { switch (_dragIntent) { case _DragIntent.none: case _DragIntent.scroll: - if (_drag == null) _startProxyDrag(); + if (_drag == null) { + _startProxyDrag(); + } _drag?.update(details); _syncShowingDetails(); @@ -152,7 +171,9 @@ class _AssetPageState extends ConsumerState { } void _endDrag(DragEndDetails details) { - if (_dragStart == null) return; + if (_dragStart == null) { + return; + } final start = _dragStart; _dragStart = null; @@ -189,7 +210,9 @@ class _AssetPageState extends ConsumerState { PhotoViewControllerBase controller, PhotoViewScaleStateController scaleStateController, ) { - if (!_showingDetails && _isZoomed) return; + if (!_showingDetails && _isZoomed) { + return; + } _beginDrag(details); } @@ -216,9 +239,11 @@ class _AssetPageState extends ConsumerState { } void _onTapUp(BuildContext context, TapUpDetails details, PhotoViewControllerValue controllerValue) { - if (_showingDetails || _dragStart != null) return; + if (_showingDetails || _dragStart != null) { + return; + } - final tapToNavigate = ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.tapToNavigate); + final tapToNavigate = ref.read(appConfigProvider).viewer.tapToNavigate; if (!tapToNavigate) { _viewer.toggleControls(); return; @@ -248,31 +273,43 @@ class _AssetPageState extends ConsumerState { _viewer.setZoomed(_isZoomed); if (scaleState != PhotoViewScaleState.initial) { - if (_dragStart == null) _viewer.setControls(false); + if (_dragStart == null) { + _viewer.setControls(false); + } return; } - if (!_showingDetails) _viewer.setControls(true); + if (!_showingDetails) { + _viewer.setControls(true); + } } void _listenForScaleBoundaries(PhotoViewControllerBase? controller) { _scaleBoundarySub?.cancel(); _scaleBoundarySub = null; - if (controller == null || controller.scaleBoundaries != null) return; + if (controller == null || controller.scaleBoundaries != null) { + return; + } _scaleBoundarySub = controller.outputStateStream.listen((_) { if (controller.scaleBoundaries != null) { _scaleBoundarySub?.cancel(); _scaleBoundarySub = null; - if (mounted) setState(() {}); + if (mounted) { + setState(() {}); + } } }); } double _getImageHeight(double maxWidth, double maxHeight, BaseAsset? asset) { final sb = _viewController?.scaleBoundaries; - if (sb != null) return sb.childSize.height * sb.initialScale; + if (sb != null) { + return sb.childSize.height * sb.initialScale; + } - if (asset == null || asset.width == null || asset.height == null) return maxHeight; + if (asset == null || asset.width == null || asset.height == null) { + return maxHeight; + } final r = asset.width! / asset.height!; return math.min(maxWidth / r, maxHeight); @@ -288,14 +325,16 @@ class _AssetPageState extends ConsumerState { required PhotoViewHeroAttributes? heroAttributes, required bool isCurrent, required bool isPlayingMotionVideo, + required String? localFilePath, }) { final size = context.sizeData; + final imageProvider = getFullImageProvider(asset, size: size, localFilePath: localFilePath); if (asset.isImage && !isPlayingMotionVideo) { return PhotoView( key: Key(asset.heroTag), index: widget.index, - imageProvider: getFullImageProvider(asset, size: size), + imageProvider: imageProvider, heroAttributes: heroAttributes, loadingBuilder: (context, progress, index) => const Center(child: ImmichLoadingIndicator()), gaplessPlayback: true, @@ -342,12 +381,9 @@ class _AssetPageState extends ConsumerState { child: NativeVideoViewer( key: _NativeVideoViewerKey(asset.heroTag), asset: asset, + localFilePath: localFilePath, isCurrent: isCurrent, - image: Image( - image: getFullImageProvider(asset, size: size), - fit: BoxFit.contain, - alignment: Alignment.center, - ), + image: Image(image: imageProvider, fit: BoxFit.contain, alignment: Alignment.center), ), ); } @@ -358,9 +394,10 @@ class _AssetPageState extends ConsumerState { _showingDetails = ref.watch(assetViewerProvider.select((s) => s.showingDetails)); final stackIndex = ref.watch(assetViewerProvider.select((s) => s.stackIndex)); final isPlayingMotionVideo = ref.watch(isPlayingMotionVideoProvider); + final timelineOrigin = ref.read(timelineServiceProvider).origin; final showingOcr = ref.watch(assetViewerProvider.select((s) => s.showingOcr)); - final asset = ref.read(timelineServiceProvider).getAssetSafe(widget.index); + final asset = _asset; if (asset == null) { return const Center(child: ImmichLoadingIndicator()); } @@ -387,6 +424,8 @@ class _AssetPageState extends ConsumerState { _scrollController.snapPosition.snapOffset = _snapOffset; } + final viewIntentFilePath = timelineOrigin == TimelineOrigin.deepLink ? ref.watch(viewIntentFilePathProvider) : null; + return Stack( children: [ SingleChildScrollView( @@ -406,6 +445,7 @@ class _AssetPageState extends ConsumerState { : null, isCurrent: isCurrent, isPlayingMotionVideo: isPlayingMotionVideo, + localFilePath: viewIntentFilePath, ), ), if (showingOcr && displayAsset.width != null && displayAsset.height != null) diff --git a/mobile/lib/presentation/widgets/asset_viewer/asset_preloader.dart b/mobile/lib/presentation/widgets/asset_viewer/asset_preloader.dart index ca7498a37f..4d1856b90d 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/asset_preloader.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/asset_preloader.dart @@ -21,12 +21,16 @@ class AssetPreloader { unawaited(timelineService.preloadAssets(index)); _timer?.cancel(); _timer = Timer(Durations.medium4, () async { - if (!mounted()) return; + if (!mounted()) { + return; + } final (prev, next) = await ( timelineService.getAssetAsync(index - 1), timelineService.getAssetAsync(index + 1), ).wait; - if (!mounted()) return; + if (!mounted()) { + return; + } _prevStream?.removeListener(_dummyListener); _nextStream?.removeListener(_dummyListener); _prevStream = prev != null ? _resolveImage(prev, size) : null; diff --git a/mobile/lib/presentation/widgets/asset_viewer/asset_viewer.page.dart b/mobile/lib/presentation/widgets/asset_viewer/asset_viewer.page.dart index 3308ae8295..c8d8f63fa9 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/asset_viewer.page.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/asset_viewer.page.dart @@ -17,9 +17,9 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/download_statu import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_page.widget.dart'; import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_preloader.dart'; import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_stack.provider.dart'; -import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'; -import 'package:immich_mobile/presentation/widgets/asset_viewer/viewer_top_app_bar.widget.dart'; import 'package:immich_mobile/presentation/widgets/asset_viewer/viewer_bottom_app_bar.widget.dart'; +import 'package:immich_mobile/presentation/widgets/asset_viewer/viewer_top_app_bar.widget.dart'; +import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'; import 'package:immich_mobile/providers/cast.provider.dart'; import 'package:immich_mobile/providers/infrastructure/current_album.provider.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; @@ -67,7 +67,9 @@ class AssetViewer extends ConsumerStatefulWidget { ref.read(assetViewerProvider.notifier).reset(); // Hide controls by default for videos - if (asset.isVideo) ref.read(assetViewerProvider.notifier).setControls(false); + if (asset.isVideo) { + ref.read(assetViewerProvider.notifier).setControls(false); + } _setAsset(ref, asset); } @@ -90,7 +92,9 @@ class _AssetViewerState extends ConsumerState { void _onTapNavigate(int direction) { final page = _pageController.page?.toInt(); - if (page == null) return; + if (page == null) { + return; + } final target = page + direction; final maxPage = _totalAssets - 1; if (target >= 0 && target <= maxPage) { @@ -105,7 +109,9 @@ class _AssetViewerState extends ConsumerState { final asset = ref.read(assetViewerProvider).currentAsset; assert(asset != null, "Current asset should not be null when opening the AssetViewer"); - if (asset != null) _stackChildrenKeepAlive = ref.read(stackChildrenNotifier(asset).notifier).ref.keepAlive(); + if (asset != null) { + _stackChildrenKeepAlive = ref.read(stackChildrenNotifier(asset).notifier).ref.keepAlive(); + } _reloadSubscription = EventStream.shared.listen(_onEvent); @@ -137,7 +143,9 @@ class _AssetViewerState extends ConsumerState { // playing, and preventing the video on the next page from becoming ready // unnecessarily. bool _onScrollEnd(ScrollEndNotification notification) { - if (notification.depth != 0) return false; + if (notification.depth != 0) { + return false; + } final page = _pageController.page?.round(); if (page != null && page != _currentPage) { @@ -155,7 +163,9 @@ class _AssetViewerState extends ConsumerState { _currentPage = index; final asset = await ref.read(timelineServiceProvider).getAssetAsync(index); - if (asset == null) return; + if (asset == null) { + return; + } AssetViewer._setAsset(ref, asset); _preloader.preload(index, context.sizeData); @@ -165,9 +175,13 @@ class _AssetViewerState extends ConsumerState { } void _handleCasting() { - if (!ref.read(castProvider).isCasting) return; + if (!ref.read(castProvider).isCasting) { + return; + } final asset = ref.read(assetViewerProvider).currentAsset; - if (asset == null) return; + if (asset == null) { + return; + } if (asset is RemoteAsset) { context.scaffoldMessenger.hideCurrentSnackBar(); @@ -199,7 +213,9 @@ class _AssetViewerState extends ConsumerState { } void _onViewerReloadEvent() { - if (_totalAssets <= 1) return; + if (_totalAssets <= 1) { + return; + } final index = _pageController.page?.round() ?? 0; final target = index >= _totalAssets - 1 ? index - 1 : index + 1; @@ -252,7 +268,9 @@ class _AssetViewerState extends ConsumerState { // Listen for casting changes and send initial asset to the cast provider ref.listen(castProvider.select((value) => value.isCasting), (_, isCasting) { - if (!isCasting) return; + if (!isCasting) { + return; + } WidgetsBinding.instance.addPostFrameCallback((_) { _handleCasting(); }); diff --git a/mobile/lib/presentation/widgets/asset_viewer/bottom_bar.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/bottom_bar.widget.dart index cf7ffbd234..e317c598f5 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/bottom_bar.widget.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/bottom_bar.widget.dart @@ -2,15 +2,19 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; +import 'package:immich_mobile/domain/services/timeline.service.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/add_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/delete_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/delete_local_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/delete_permanent_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/edit_image_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/restore_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/share_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/upload_action_button.widget.dart'; import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'; import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; import 'package:immich_mobile/providers/routes.provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; @@ -33,23 +37,31 @@ class ViewerBottomBar extends ConsumerWidget { final showingDetails = ref.watch(assetViewerProvider.select((s) => s.showingDetails)); final isInLockedView = ref.watch(inLockedViewProvider); final serverInfo = ref.watch(serverInfoProvider); + final isInTrash = ref.read(timelineServiceProvider).origin == TimelineOrigin.trash; final originalTheme = context.themeData; final actions = [ - const ShareActionButton(source: ActionSource.viewer), + if (isInTrash && isOwner && asset.hasRemote) + const RestoreActionButton(source: ActionSource.viewer) + else + const ShareActionButton(source: ActionSource.viewer), if (!isInLockedView) ...[ - if (asset.isLocalOnly) const UploadActionButton(source: ActionSource.viewer), - // edit sync was added in 2.6.0 - if (asset.isEditable && serverInfo.serverVersion >= const SemVer(major: 2, minor: 6, patch: 0)) - const EditImageActionButton(), - if (asset.hasRemote) AddActionButton(originalTheme: originalTheme), - + if (!isInTrash) ...[ + if (asset.isLocalOnly) const UploadActionButton(source: ActionSource.viewer), + // edit sync was added in 2.6.0 + if (asset.isEditable && serverInfo.serverVersion >= const SemVer(major: 2, minor: 6, patch: 0)) + const EditImageActionButton(), + if (asset.hasRemote) AddActionButton(originalTheme: originalTheme), + ], if (isOwner) ...[ - asset.isLocalOnly - ? const DeleteLocalActionButton(source: ActionSource.viewer) - : const DeleteActionButton(source: ActionSource.viewer, showConfirmation: true), + if (asset.isLocalOnly) + const DeleteLocalActionButton(source: ActionSource.viewer) + else if (asset.isTrashed) + const DeletePermanentActionButton(source: ActionSource.viewer, useShortLabel: true) + else + const DeleteActionButton(source: ActionSource.viewer, showConfirmation: true), ], ], ]; @@ -65,26 +77,37 @@ class ViewerBottomBar extends ConsumerWidget { labelLarge: context.themeData.textTheme.labelLarge?.copyWith(color: Colors.white), ), ), - child: Container( - decoration: const BoxDecoration( - gradient: LinearGradient( - begin: Alignment.bottomCenter, - end: Alignment.topCenter, - colors: [Colors.black45, Colors.black12, Colors.transparent], - stops: [0.0, 0.7, 1.0], + child: Stack( + children: [ + const Positioned.fill( + child: IgnorePointer( + child: DecoratedBox( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.bottomCenter, + end: Alignment.topCenter, + colors: [Colors.black45, Colors.black12, Colors.transparent], + stops: [0.0, 0.7, 1.0], + ), + ), + ), + ), ), - ), - child: SafeArea( - top: false, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - if (asset.isVideo) VideoControls(videoPlayerName: asset.heroTag), - if (!isReadonlyModeEnabled) - Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: actions), - ], + SafeArea( + top: false, + child: Padding( + padding: const EdgeInsets.only(top: 16), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (asset.isVideo) VideoControls(videoPlayerName: asset.heroTag), + if (!isReadonlyModeEnabled) + Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: actions), + ], + ), + ), ), - ), + ], ), ), ); diff --git a/mobile/lib/presentation/widgets/asset_viewer/motion_photo_button.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/motion_photo_button.widget.dart new file mode 100644 index 0000000000..800af23039 --- /dev/null +++ b/mobile/lib/presentation/widgets/asset_viewer/motion_photo_button.widget.dart @@ -0,0 +1,78 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/platform_extensions.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'; +import 'package:immich_mobile/providers/asset_viewer/is_motion_video_playing.provider.dart'; + +class MotionPhotoPlayButton extends ConsumerWidget { + const MotionPhotoPlayButton({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final asset = ref.watch(assetViewerProvider.select((state) => state.currentAsset)); + final isPlaying = ref.watch(isPlayingMotionVideoProvider); + final showControls = ref.watch(assetViewerProvider.select((state) => state.showingControls)); + final isShowingDetails = ref.watch(assetViewerProvider.select((state) => state.showingDetails)); + + if (asset == null || !asset.isMotionPhoto || isShowingDetails) { + return const SizedBox.shrink(); + } + + return IgnorePointer( + ignoring: !showControls, + child: AnimatedOpacity( + opacity: showControls ? 1.0 : 0.0, + duration: Durations.short2, + child: SafeArea( + child: Padding( + padding: const EdgeInsets.only(top: 60), + child: Center( + child: _MotionButton( + isPlaying: isPlaying, + onPressed: ref.read(isPlayingMotionVideoProvider.notifier).toggle, + ), + ), + ), + ), + ), + ); + } +} + +class _MotionButton extends StatelessWidget { + final bool isPlaying; + final VoidCallback onPressed; + + const _MotionButton({required this.isPlaying, required this.onPressed}); + + @override + Widget build(BuildContext context) { + return Material( + color: Colors.grey[900]!.withValues(alpha: 0.4), + borderRadius: const BorderRadius.all(Radius.circular(24)), + child: InkWell( + onTap: onPressed, + borderRadius: const BorderRadius.all(Radius.circular(24)), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + isPlaying ? Icons.motion_photos_pause_outlined : Icons.play_circle_outline_rounded, + color: Colors.white, + size: 16, + ), + const SizedBox(width: 8), + Text( + CurrentPlatform.isAndroid ? 'motion'.t(context: context) : 'live'.t(context: context), + style: const TextStyle(color: Colors.white, fontSize: 14, fontWeight: FontWeight.w500), + ), + ], + ), + ), + ), + ); + } +} diff --git a/mobile/lib/presentation/widgets/asset_viewer/ocr_overlay.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/ocr_overlay.widget.dart index cf44f3185f..86941a4550 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/ocr_overlay.widget.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/ocr_overlay.widget.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:math' as math; + import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -58,7 +59,9 @@ class _OcrOverlayState extends ConsumerState { } void _attachController(PhotoViewControllerBase? controller) { - if (controller == null) return; + if (controller == null) { + return; + } // Seed with the current value only when scaleBoundaries is already set. // Before the image finishes loading, PhotoView uses childSize = outerSize @@ -72,7 +75,9 @@ class _OcrOverlayState extends ConsumerState { } _controllerSub = controller.outputStateStream.listen((value) { - if (mounted) setState(() => _controllerValue = value); + if (mounted) { + setState(() => _controllerValue = value); + } }); } @@ -276,10 +281,7 @@ class _OcrBoxItem extends StatelessWidget { borderRadius: const BorderRadius.all(Radius.circular(4)), ), child: ConstrainedBox( - constraints: BoxConstraints( - maxWidth: math.max(50, width), - maxHeight: math.max(20, height), - ), + constraints: BoxConstraints(maxWidth: math.max(50, width), maxHeight: math.max(20, height)), child: FittedBox( fit: BoxFit.scaleDown, child: SelectableText( diff --git a/mobile/lib/presentation/widgets/asset_viewer/rating_bar.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/rating_bar.widget.dart index 62a439fe39..bd4935e41f 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/rating_bar.widget.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/rating_bar.widget.dart @@ -53,7 +53,9 @@ class _RatingBarState extends State { final totalWidth = widget.itemCount * widget.itemSize + (widget.itemCount - 1) * widget.starPadding; double dx = localPosition.dx; - if (isRTL) dx = totalWidth - dx; + if (isRTL) { + dx = totalWidth - dx; + } double newRating; diff --git a/mobile/lib/presentation/widgets/asset_viewer/sheet_tile.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/sheet_tile.widget.dart index 2af68e1ff0..69e84ee03d 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/sheet_tile.widget.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/sheet_tile.widget.dart @@ -63,16 +63,19 @@ class SheetTile extends ConsumerWidget { subtitleWidget = null; } - return ListTile( - dense: true, - visualDensity: VisualDensity.compact, - title: GestureDetector(onLongPress: () => copyTitle(context, ref), child: titleWidget), - titleAlignment: ListTileTitleAlignment.center, - leading: leading, - trailing: trailing, - contentPadding: leading == null ? null : const EdgeInsets.only(left: 25), - subtitle: subtitleWidget, - onTap: onTap, + return Material( + type: MaterialType.transparency, + child: ListTile( + dense: true, + visualDensity: VisualDensity.compact, + title: GestureDetector(onLongPress: () => copyTitle(context, ref), child: titleWidget), + titleAlignment: ListTileTitleAlignment.center, + leading: leading, + trailing: trailing, + contentPadding: leading == null ? null : const EdgeInsets.only(left: 25), + subtitle: subtitleWidget, + onTap: onTap, + ), ); } } diff --git a/mobile/lib/presentation/widgets/asset_viewer/video_viewer.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/video_viewer.widget.dart index fb5c02599d..2be7bb91e8 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/video_viewer.widget.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/video_viewer.widget.dart @@ -1,28 +1,26 @@ import 'dart:async'; +import 'dart:io'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/domain/models/setting.model.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; -import 'package:immich_mobile/domain/services/setting.service.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/platform_extensions.dart'; import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart'; import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'; -import 'package:immich_mobile/providers/app_settings.provider.dart'; import 'package:immich_mobile/providers/asset_viewer/is_motion_video_playing.provider.dart'; import 'package:immich_mobile/providers/asset_viewer/video_player_provider.dart'; import 'package:immich_mobile/providers/cast.provider.dart'; import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/setting.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; import 'package:immich_mobile/services/api.service.dart'; -import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:logging/logging.dart'; import 'package:native_video_player/native_video_player.dart'; class NativeVideoViewer extends ConsumerStatefulWidget { final BaseAsset asset; + final String? localFilePath; final bool isCurrent; final bool showControls; final Widget image; @@ -30,6 +28,7 @@ class NativeVideoViewer extends ConsumerStatefulWidget { const NativeVideoViewer({ super.key, required this.asset, + this.localFilePath, required this.image, this.isCurrent = false, this.showControls = true, @@ -61,7 +60,9 @@ class _NativeVideoViewerState extends ConsumerState with Widg void didUpdateWidget(NativeVideoViewer oldWidget) { super.didUpdateWidget(oldWidget); - if (widget.isCurrent == oldWidget.isCurrent || _controller == null) return; + if (widget.isCurrent == oldWidget.isCurrent || _controller == null) { + return; + } if (!widget.isCurrent) { _loadTimer?.cancel(); @@ -85,25 +86,48 @@ class _NativeVideoViewerState extends ConsumerState with Widg void didChangeAppLifecycleState(AppLifecycleState state) async { switch (state) { case AppLifecycleState.resumed: - if (_shouldPlayOnForeground) await _notifier.play(); + if (_shouldPlayOnForeground) { + await _notifier.play(); + } case AppLifecycleState.paused: _shouldPlayOnForeground = await _controller?.isPlaying() ?? true; - if (_shouldPlayOnForeground) await _notifier.pause(); + if (_shouldPlayOnForeground) { + await _notifier.pause(); + } default: } } Future _createSource() async { - if (!mounted) return null; + if (!mounted) { + return null; + } final videoAsset = await ref.read(assetServiceProvider).getAsset(widget.asset) ?? widget.asset; - if (!mounted) return null; + if (!mounted) { + return null; + } try { + final localFilePath = widget.localFilePath; + if (localFilePath != null) { + final file = File(localFilePath); + if (!await file.exists()) { + throw Exception('No file found for the video'); + } + + return VideoSource.init( + path: CurrentPlatform.isAndroid ? file.uri.toString() : file.path, + type: VideoSourceType.file, + ); + } + if (videoAsset.hasLocal && videoAsset.livePhotoVideoId == null) { final id = videoAsset is LocalAsset ? videoAsset.id : (videoAsset as RemoteAsset).localId!; final file = await StorageRepository().getFileForAsset(id); - if (!mounted) return null; + if (!mounted) { + return null; + } if (file == null) { throw Exception('No file found for the video'); @@ -120,7 +144,7 @@ class _NativeVideoViewerState extends ConsumerState with Widg final remoteId = (videoAsset as RemoteAsset).id; final serverEndpoint = Store.get(StoreKey.serverEndpoint); - final isOriginalVideo = ref.read(settingsProvider).get(Setting.loadOriginalVideo); + final isOriginalVideo = ref.read(appConfigProvider).viewer.loadOriginalVideo; final String postfixUrl = isOriginalVideo ? 'original' : 'video/playback'; final String videoUrl = videoAsset.livePhotoVideoId != null ? '$serverEndpoint/assets/${videoAsset.livePhotoVideoId}/$postfixUrl' @@ -134,25 +158,35 @@ class _NativeVideoViewerState extends ConsumerState with Widg } void _onPlaybackReady() async { - if (!mounted || !widget.isCurrent) return; + if (!mounted || !widget.isCurrent) { + return; + } _notifier.onNativePlaybackReady(); // onPlaybackReady may be called multiple times, usually when more data // loads. If this is not the first time that the player has become ready, we // should not autoplay. - if (_isVideoReady) return; + if (_isVideoReady) { + return; + } setState(() => _isVideoReady = true); - if (ref.read(assetViewerProvider).showingDetails) return; + if (ref.read(assetViewerProvider).showingDetails) { + return; + } - final autoPlayVideo = AppSetting.get(Setting.autoPlayVideo); - if (autoPlayVideo || widget.asset.isMotionPhoto) await _notifier.play(); + final autoPlayVideo = ref.read(appConfigProvider).viewer.autoPlayVideo; + if (autoPlayVideo || widget.asset.isMotionPhoto) { + await _notifier.play(); + } } void _onPlaybackEnded() { - if (!mounted) return; + if (!mounted) { + return; + } _notifier.onNativePlaybackEnded(); @@ -162,12 +196,16 @@ class _NativeVideoViewerState extends ConsumerState with Widg } void _onPlaybackPositionChanged() { - if (!mounted) return; + if (!mounted) { + return; + } _notifier.onNativePositionChanged(); } void _onPlaybackStatusChanged() { - if (!mounted) return; + if (!mounted) { + return; + } _notifier.onNativeStatusChanged(); } @@ -180,19 +218,25 @@ class _NativeVideoViewerState extends ConsumerState with Widg void _loadVideo() async { final nc = _controller; - if (nc == null || nc.videoSource != null || !mounted) return; + if (nc == null || nc.videoSource != null || !mounted) { + return; + } final source = await _videoSource; - if (source == null || !mounted) return; + if (source == null || !mounted) { + return; + } await _notifier.load(source); - final loopVideo = ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.loopVideo); + final loopVideo = ref.read(appConfigProvider).viewer.loopVideo; await _notifier.setLoop(!widget.asset.isMotionPhoto && loopVideo); await _notifier.setVolume(1); } void _initController(NativeVideoPlayerController nc) { - if (_controller != null || !mounted) return; + if (_controller != null || !mounted) { + return; + } _notifier.attachController(nc); @@ -203,7 +247,9 @@ class _NativeVideoViewerState extends ConsumerState with Widg _controller = nc; - if (widget.isCurrent) _loadVideo(); + if (widget.isCurrent) { + _loadVideo(); + } } @override diff --git a/mobile/lib/presentation/widgets/asset_viewer/viewer_kebab_menu.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/viewer_kebab_menu.widget.dart index 78b2e50da5..418d41e1f2 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/viewer_kebab_menu.widget.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/viewer_kebab_menu.widget.dart @@ -2,10 +2,9 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/domain/models/setting.model.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/providers/cast.provider.dart'; import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'; +import 'package:immich_mobile/providers/cast.provider.dart'; import 'package:immich_mobile/providers/infrastructure/current_album.provider.dart'; import 'package:immich_mobile/providers/infrastructure/setting.provider.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; @@ -34,7 +33,7 @@ class ViewerKebabMenu extends ConsumerWidget { final isInLockedView = ref.watch(inLockedViewProvider); final currentAlbum = ref.watch(currentRemoteAlbumProvider); final isArchived = asset is RemoteAsset && asset.visibility == AssetVisibility.archive; - final advancedTroubleshooting = ref.watch(settingsProvider.notifier).get(Setting.advancedTroubleshooting); + final advancedTroubleshooting = ref.watch(settingsProvider.notifier).get(.advancedTroubleshooting); final actionContext = ActionButtonContext( asset: asset, @@ -48,7 +47,6 @@ class ViewerKebabMenu extends ConsumerWidget { source: ActionSource.viewer, isCasting: isCasting, timelineOrigin: timelineOrigin, - originalTheme: originalTheme, ); final menuChildren = ActionButtonBuilder.buildViewerKebabMenu(actionContext, context, ref); @@ -67,10 +65,13 @@ class ViewerKebabMenu extends ConsumerWidget { menuChildren: [ ConstrainedBox( constraints: const BoxConstraints(minWidth: 150), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: menuChildren, + child: Theme( + data: originalTheme ?? context.themeData, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: menuChildren, + ), ), ), ], diff --git a/mobile/lib/presentation/widgets/asset_viewer/viewer_top_app_bar.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/viewer_top_app_bar.widget.dart index 485edf4516..f7ccf3ba12 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/viewer_top_app_bar.widget.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/viewer_top_app_bar.widget.dart @@ -1,21 +1,24 @@ import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/providers/infrastructure/ocr.provider.dart'; -import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/favorite_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/motion_photo_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/unfavorite_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/asset_viewer/viewer_kebab_menu.widget.dart'; import 'package:immich_mobile/providers/activity.provider.dart'; import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/asset_viewer/asset.provider.dart'; import 'package:immich_mobile/providers/infrastructure/current_album.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/ocr.provider.dart'; import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart'; import 'package:immich_mobile/providers/routes.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; +import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/utils/timezone.dart'; class ViewerTopAppBar extends ConsumerWidget implements PreferredSizeWidget { const ViewerTopAppBar({super.key}); @@ -84,29 +87,42 @@ class ViewerTopAppBar extends ConsumerWidget implements PreferredSizeWidget { child: AnimatedOpacity( opacity: opacity, duration: Durations.short2, - child: DecoratedBox( - decoration: BoxDecoration( - gradient: showingDetails - ? null - : const LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [Colors.black45, Colors.black12, Colors.transparent], - stops: [0.0, 0.7, 1.0], + child: Stack( + children: [ + Positioned.fill( + child: IgnorePointer( + child: DecoratedBox( + decoration: BoxDecoration( + gradient: showingDetails + ? null + : const LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Colors.black45, Colors.black12, Colors.transparent], + stops: [0.0, 0.7, 1.0], + ), ), - ), - child: AppBar( - backgroundColor: Colors.transparent, - leading: const _AppBarBackButton(), - iconTheme: const IconThemeData(size: 22, color: Colors.white), - actionsIconTheme: const IconThemeData(size: 22, color: Colors.white), - shape: const Border(), - actions: showingDetails || isReadonlyModeEnabled - ? null - : isInLockedView - ? lockedViewActions - : actions, - ), + ), + ), + ), + SafeArea( + bottom: false, + child: SizedBox( + height: preferredSize.height, + child: Theme( + data: context.themeData.copyWith(iconTheme: const IconThemeData(size: 22, color: Colors.white)), + child: NavigationToolbar( + centerMiddle: true, + leading: const _AppBarBackButton(), + middle: showingDetails ? null : _AssetInfoTitle(asset: asset), + trailing: !showingDetails && !isReadonlyModeEnabled + ? Row(mainAxisSize: MainAxisSize.min, children: isInLockedView ? lockedViewActions : actions) + : null, + ), + ), + ), + ), + ], ), ), ); @@ -122,20 +138,46 @@ class _AppBarBackButton extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final showingDetails = ref.watch(assetViewerProvider.select((state) => state.showingDetails)); - return Padding( - padding: const EdgeInsets.only(left: 12.0), - child: ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: showingDetails ? context.colorScheme.surface : Colors.transparent, - shape: const CircleBorder(), - iconSize: 22, - iconColor: showingDetails ? context.colorScheme.onSurface : Colors.white, - padding: EdgeInsets.zero, - elevation: showingDetails ? 4 : 0, - ), - onPressed: context.maybePop, - child: const Icon(Icons.arrow_back_rounded), + return ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: showingDetails ? context.colorScheme.surface : Colors.transparent, + shape: const CircleBorder(), + iconSize: 22, + iconColor: showingDetails ? context.colorScheme.onSurface : Colors.white, + padding: const EdgeInsets.all(10.0), + elevation: showingDetails ? 4 : 0, ), + onPressed: context.maybePop, + child: const Icon(Icons.arrow_back_rounded), + ); + } +} + +class _AssetInfoTitle extends ConsumerWidget { + final BaseAsset asset; + + const _AssetInfoTitle({required this.asset}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + DateTime dateTime = asset.createdAt.toLocal(); + final currentYear = DateTime.now().year; + final exifInfo = ref.watch(assetExifProvider(asset)).valueOrNull; + + if (exifInfo?.dateTimeOriginal != null) { + (dateTime, _) = applyTimezoneOffset(dateTime: exifInfo!.dateTimeOriginal!, timeZone: exifInfo.timeZone); + } + + final isCurrentYear = dateTime.year == currentYear; + final dateFormatted = isCurrentYear ? DateFormat.MMMd().format(dateTime) : DateFormat.yMMMd().format(dateTime); + final timeFormatted = DateFormat.jm().format(dateTime); + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text(dateFormatted, style: context.textTheme.labelLarge?.copyWith(color: Colors.white)), + Text(timeFormatted, style: context.textTheme.labelMedium?.copyWith(color: Colors.white70)), + ], ); } } diff --git a/mobile/lib/presentation/widgets/backup/backup_toggle_button.widget.dart b/mobile/lib/presentation/widgets/backup/backup_toggle_button.widget.dart index 7c92dc01d8..3bf48783ea 100644 --- a/mobile/lib/presentation/widgets/backup/backup_toggle_button.widget.dart +++ b/mobile/lib/presentation/widgets/backup/backup_toggle_button.widget.dart @@ -2,9 +2,8 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; -import 'package:immich_mobile/providers/app_settings.provider.dart'; import 'package:immich_mobile/providers/backup/drift_backup.provider.dart'; -import 'package:immich_mobile/services/app_settings.service.dart'; +import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; class BackupToggleButton extends ConsumerStatefulWidget { final VoidCallback onStart; @@ -31,7 +30,7 @@ class BackupToggleButtonState extends ConsumerState with Sin end: 1, ).animate(CurvedAnimation(parent: _animationController, curve: Curves.easeInOut)); - _isEnabled = ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.enableBackup); + _isEnabled = ref.read(appConfigProvider).backup.enabled; } @override @@ -41,7 +40,7 @@ class BackupToggleButtonState extends ConsumerState with Sin } Future _onToggle(bool value) async { - await ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.enableBackup, value); + await ref.read(settingsProvider).write(.backupEnabled, value); setState(() { _isEnabled = value; diff --git a/mobile/lib/presentation/widgets/bottom_sheet/general_bottom_sheet.widget.dart b/mobile/lib/presentation/widgets/bottom_sheet/general_bottom_sheet.widget.dart index 8753a9c14f..c3a569407a 100644 --- a/mobile/lib/presentation/widgets/bottom_sheet/general_bottom_sheet.widget.dart +++ b/mobile/lib/presentation/widgets/bottom_sheet/general_bottom_sheet.widget.dart @@ -3,11 +3,10 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/domain/models/album/album.model.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/setting.model.dart'; -import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/advanced_info_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/archive_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/bulk_tag_assets_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/delete_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/delete_local_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/delete_permanent_action_button.widget.dart'; @@ -24,8 +23,9 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/unstack_action import 'package:immich_mobile/presentation/widgets/action_buttons/upload_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/album/album_selector.widget.dart'; import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart'; -import 'package:immich_mobile/providers/infrastructure/album.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; import 'package:immich_mobile/providers/infrastructure/setting.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/user_metadata.provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; @@ -57,38 +57,27 @@ class _GeneralBottomSheetState extends ConsumerState { final multiselect = ref.watch(multiSelectProvider); final isTrashEnable = ref.watch(serverInfoProvider.select((state) => state.serverFeatures.trash)); final advancedTroubleshooting = ref.watch(settingsProvider.notifier).get(Setting.advancedTroubleshooting); + final tagsEnabled = ref.watch( + userMetadataPreferencesProvider.select((value) => value.valueOrNull?.tagsEnabled ?? false), + ); - Future addAssetsToAlbum(RemoteAlbum album) async { - final selectedAssets = multiselect.selectedAssets; - if (selectedAssets.isEmpty) { + Future addToAlbum(RemoteAlbum album) async { + final result = await ref.read(actionProvider.notifier).addToAlbum(ActionSource.timeline, album); + + if (!context.mounted) { return; } - final remoteAssets = selectedAssets.whereType(); - final addedCount = await ref - .read(remoteAlbumProvider.notifier) - .addAssets(album.id, remoteAssets.map((e) => e.id).toList()); - - if (selectedAssets.length != remoteAssets.length) { - ImmichToast.show( - context: context, - msg: 'add_to_album_bottom_sheet_some_local_assets'.t(context: context), - ); + if (!result.success) { + ImmichToast.show(context: context, msg: 'scaffold_body_error_occurred'.tr(), toastType: ToastType.error); + return; } - - if (addedCount != remoteAssets.length) { - ImmichToast.show( - context: context, - msg: 'add_to_album_bottom_sheet_already_exists'.tr(namedArgs: {"album": album.name}), - ); - } else { - ImmichToast.show( - context: context, - msg: 'add_to_album_bottom_sheet_added'.tr(namedArgs: {"album": album.name}), - ); - } - - ref.read(multiSelectProvider.notifier).reset(); + ImmichToast.show( + context: context, + msg: result.count == 0 + ? 'add_to_album_bottom_sheet_already_exists'.tr(namedArgs: {'album': album.name}) + : 'add_to_album_bottom_sheet_added'.tr(namedArgs: {'album': album.name}), + ); } Future onKeyboardExpand() { @@ -114,6 +103,7 @@ class _GeneralBottomSheetState extends ConsumerState { : const DeletePermanentActionButton(source: ActionSource.timeline), const FavoriteActionButton(source: ActionSource.timeline), const ArchiveActionButton(source: ActionSource.timeline), + if (tagsEnabled) const BulkTagAssetsActionButton(source: ActionSource.timeline), const EditDateTimeActionButton(source: ActionSource.timeline), const EditLocationActionButton(source: ActionSource.timeline), const MoveToLockFolderActionButton(source: ActionSource.timeline), @@ -125,12 +115,10 @@ class _GeneralBottomSheetState extends ConsumerState { const DeleteLocalActionButton(source: ActionSource.timeline), if (multiselect.onlyLocal) const UploadActionButton(source: ActionSource.timeline), ], - slivers: multiselect.hasRemote - ? [ - const AddToAlbumHeader(), - AlbumSelector(onAlbumSelected: addAssetsToAlbum, onKeyboardExpanded: onKeyboardExpand), - ] - : [], + slivers: [ + const AddToAlbumHeader(), + AlbumSelector(onAlbumSelected: addToAlbum, onKeyboardExpanded: onKeyboardExpand), + ], ); } } diff --git a/mobile/lib/presentation/widgets/bottom_sheet/local_album_bottom_sheet.widget.dart b/mobile/lib/presentation/widgets/bottom_sheet/local_album_bottom_sheet.widget.dart index b1e87dfaea..ac8c77af03 100644 --- a/mobile/lib/presentation/widgets/bottom_sheet/local_album_bottom_sheet.widget.dart +++ b/mobile/lib/presentation/widgets/bottom_sheet/local_album_bottom_sheet.widget.dart @@ -1,25 +1,78 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/domain/models/album/album.model.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/delete_local_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/share_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/upload_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/album/album_selector.widget.dart'; import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart'; +import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; +import 'package:immich_mobile/widgets/common/immich_toast.dart'; -class LocalAlbumBottomSheet extends ConsumerWidget { +class LocalAlbumBottomSheet extends ConsumerStatefulWidget { const LocalAlbumBottomSheet({super.key}); @override - Widget build(BuildContext context, WidgetRef ref) { - return const BaseBottomSheet( + ConsumerState createState() => _LocalAlbumBottomSheetState(); +} + +class _LocalAlbumBottomSheetState extends ConsumerState { + late final DraggableScrollableController sheetController; + + @override + void initState() { + super.initState(); + sheetController = DraggableScrollableController(); + } + + @override + void dispose() { + sheetController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + Future addToAlbum(RemoteAlbum album) async { + final result = await ref.read(actionProvider.notifier).addToAlbum(ActionSource.timeline, album); + + if (!context.mounted) { + return; + } + + if (!result.success) { + ImmichToast.show(context: context, msg: 'scaffold_body_error_occurred'.tr(), toastType: ToastType.error); + return; + } + + ImmichToast.show( + context: context, + msg: result.count == 0 + ? 'add_to_album_bottom_sheet_already_exists'.tr(namedArgs: {'album': album.name}) + : 'add_to_album_bottom_sheet_added'.tr(namedArgs: {'album': album.name}), + ); + } + + Future onKeyboardExpand() { + return sheetController.animateTo(0.85, duration: const Duration(milliseconds: 200), curve: Curves.easeInOut); + } + + return BaseBottomSheet( + controller: sheetController, initialChildSize: 0.25, - maxChildSize: 0.4, + maxChildSize: 0.85, shouldCloseOnMinExtent: false, - actions: [ + actions: const [ ShareActionButton(source: ActionSource.timeline), DeleteLocalActionButton(source: ActionSource.timeline), UploadActionButton(source: ActionSource.timeline), ], + slivers: [ + const AddToAlbumHeader(), + AlbumSelector(onAlbumSelected: addToAlbum, onKeyboardExpanded: onKeyboardExpand), + ], ); } } diff --git a/mobile/lib/presentation/widgets/bottom_sheet/remote_album_bottom_sheet.widget.dart b/mobile/lib/presentation/widgets/bottom_sheet/remote_album_bottom_sheet.widget.dart index 6848a07bb8..6b914ed077 100644 --- a/mobile/lib/presentation/widgets/bottom_sheet/remote_album_bottom_sheet.widget.dart +++ b/mobile/lib/presentation/widgets/bottom_sheet/remote_album_bottom_sheet.widget.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/domain/models/album/album.model.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/archive_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/delete_local_action_button.widget.dart'; @@ -21,7 +20,7 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/trash_action_b import 'package:immich_mobile/presentation/widgets/action_buttons/unstack_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/album/album_selector.widget.dart'; import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart'; -import 'package:immich_mobile/providers/infrastructure/album.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; @@ -56,29 +55,28 @@ class _RemoteAlbumBottomSheetState extends ConsumerState final isTrashEnable = ref.watch(serverInfoProvider.select((state) => state.serverFeatures.trash)); final ownsAlbum = ref.watch(currentUserProvider)?.id == widget.album.ownerId; - Future addAssetsToAlbum(RemoteAlbum album) async { - final selectedAssets = multiselect.selectedAssets; - if (selectedAssets.isEmpty) { + Future addToAlbum(RemoteAlbum album) async { + final result = await ref.read(actionProvider.notifier).addToAlbum(ActionSource.timeline, album); + + if (!context.mounted) { return; } - final addedCount = await ref - .read(remoteAlbumProvider.notifier) - .addAssets(album.id, selectedAssets.map((e) => (e as RemoteAsset).id).toList()); - - if (addedCount != selectedAssets.length) { + if (!result.success) { ImmichToast.show( context: context, - msg: 'add_to_album_bottom_sheet_already_exists'.t(context: context, args: {"album": album.name}), - ); - } else { - ImmichToast.show( - context: context, - msg: 'add_to_album_bottom_sheet_added'.t(context: context, args: {"album": album.name}), + msg: 'scaffold_body_error_occurred'.t(context: context), + toastType: ToastType.error, ); + return; } - ref.read(multiSelectProvider.notifier).reset(); + ImmichToast.show( + context: context, + msg: result.count == 0 + ? 'add_to_album_bottom_sheet_already_exists'.t(context: context, args: {"album": album.name}) + : 'add_to_album_bottom_sheet_added'.t(context: context, args: {"album": album.name}), + ); } Future onKeyboardExpand() { @@ -118,10 +116,7 @@ class _RemoteAlbumBottomSheetState extends ConsumerState SetAlbumCoverActionButton(source: ActionSource.timeline, albumId: widget.album.id), ], slivers: ownsAlbum - ? [ - const AddToAlbumHeader(), - AlbumSelector(onAlbumSelected: addAssetsToAlbum, onKeyboardExpanded: onKeyboardExpand), - ] + ? [const AddToAlbumHeader(), AlbumSelector(onAlbumSelected: addToAlbum, onKeyboardExpanded: onKeyboardExpand)] : null, ); } diff --git a/mobile/lib/presentation/widgets/images/image_provider.dart b/mobile/lib/presentation/widgets/images/image_provider.dart index ea416d9d71..b3c58314db 100644 --- a/mobile/lib/presentation/widgets/images/image_provider.dart +++ b/mobile/lib/presentation/widgets/images/image_provider.dart @@ -1,11 +1,11 @@ +import 'dart:io'; import 'dart:ui' as ui; import 'package:async/async.dart'; import 'package:flutter/widgets.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/domain/models/setting.model.dart'; -import 'package:immich_mobile/domain/services/setting.service.dart'; import 'package:immich_mobile/infrastructure/loaders/image_request.dart'; +import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart'; import 'package:immich_mobile/presentation/widgets/images/local_image_provider.dart'; import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart'; import 'package:immich_mobile/presentation/widgets/timeline/constants.dart'; @@ -147,10 +147,17 @@ mixin CancellableImageProviderMixin on CancellableImageProvide } } -ImageProvider getFullImageProvider(BaseAsset asset, {Size size = const Size(1080, 1920), bool edited = true}) { +ImageProvider getFullImageProvider( + BaseAsset asset, { + Size size = const Size(1080, 1920), + bool edited = true, + String? localFilePath, +}) { // Create new provider and cache it final ImageProvider provider; - if (_shouldUseLocalAsset(asset)) { + if (localFilePath != null) { + provider = FileImage(File(localFilePath)); + } else if (_shouldUseLocalAsset(asset)) { final id = asset is LocalAsset ? asset.id : (asset as RemoteAsset).localId!; provider = LocalFullImageProvider(id: id, size: size, assetType: asset.type, isAnimated: asset.isAnimatedImage); } else { @@ -189,4 +196,6 @@ ImageProvider? getThumbnailImageProvider(BaseAsset asset, {Size size = kThumbnai } bool _shouldUseLocalAsset(BaseAsset asset) => - asset.hasLocal && (!asset.hasRemote || !AppSetting.get(Setting.preferRemoteImage)) && !asset.isEdited; + asset.hasLocal && + (!asset.hasRemote || !SettingsRepository.instance.appConfig.image.preferRemote) && + !asset.isEdited; diff --git a/mobile/lib/presentation/widgets/images/local_image_provider.dart b/mobile/lib/presentation/widgets/images/local_image_provider.dart index d29a1cd56d..eba4f0a1cd 100644 --- a/mobile/lib/presentation/widgets/images/local_image_provider.dart +++ b/mobile/lib/presentation/widgets/images/local_image_provider.dart @@ -1,9 +1,8 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/domain/models/store.model.dart'; -import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/infrastructure/loaders/image_request.dart'; +import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart'; import 'package:immich_mobile/presentation/widgets/images/animated_image_stream_completer.dart'; import 'package:immich_mobile/presentation/widgets/images/image_provider.dart'; import 'package:immich_mobile/presentation/widgets/images/one_frame_multi_image_stream_completer.dart'; @@ -41,7 +40,9 @@ class LocalThumbProvider extends CancellableImageProvider @override bool operator ==(Object other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } if (other is LocalThumbProvider) { return id == other.id; } @@ -103,7 +104,7 @@ class LocalFullImageProvider extends CancellableImageProvider @override bool operator ==(Object other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } if (other is RemoteImageProvider) { return url == other.url && edited == other.edited; } @@ -121,7 +122,7 @@ class RemoteFullImageProvider extends CancellableImageProvider @override bool operator ==(Object other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } if (other is ThumbHashProvider) { return thumbHash == other.thumbHash; } diff --git a/mobile/lib/presentation/widgets/images/thumbnail.widget.dart b/mobile/lib/presentation/widgets/images/thumbnail.widget.dart index 70a9057e12..847fa4e381 100644 --- a/mobile/lib/presentation/widgets/images/thumbnail.widget.dart +++ b/mobile/lib/presentation/widgets/images/thumbnail.widget.dart @@ -82,7 +82,9 @@ class _ThumbnailState extends State with SingleTickerProviderStateMix void _loadFromThumbhashProvider() { _stopListeningToThumbhashStream(); final thumbhashProvider = widget.thumbhashProvider; - if (thumbhashProvider == null || _providerImage != null) return; + if (thumbhashProvider == null || _providerImage != null) { + return; + } final thumbhashStream = _thumbhashStream = thumbhashProvider.resolve(ImageConfiguration.empty); final thumbhashStreamListener = _thumbhashStreamListener = ImageStreamListener( @@ -108,7 +110,9 @@ class _ThumbnailState extends State with SingleTickerProviderStateMix void _loadFromImageProvider() { _stopListeningToImageStream(); final imageProvider = widget.imageProvider; - if (imageProvider == null) return; + if (imageProvider == null) { + return; + } final imageStream = _imageStream = imageProvider.resolve(ImageConfiguration.empty); final imageStreamListener = _imageStreamListener = ImageStreamListener( @@ -201,7 +205,9 @@ class _ThumbnailState extends State with SingleTickerProviderStateMix bool _isVisible() { final renderObject = context.findRenderObject() as RenderBox?; - if (renderObject == null || !renderObject.attached) return false; + if (renderObject == null || !renderObject.attached) { + return false; + } final topLeft = renderObject.localToGlobal(Offset.zero); final bottomRight = renderObject.localToGlobal(Offset(renderObject.size.width, renderObject.size.height)); @@ -290,16 +296,12 @@ class _ThumbnailRenderBox extends RenderBox { bool isRepaintBoundary = true; _ThumbnailRenderBox({ - required ui.Image? image, - required ui.Image? previousImage, - required double fadeValue, - required BoxFit fit, - required Gradient placeholderGradient, - }) : _image = image, - _previousImage = previousImage, - _fadeValue = fadeValue, - _fit = fit, - _placeholderGradient = placeholderGradient; + required this._image, + required this._previousImage, + required this._fadeValue, + required this._fit, + required this._placeholderGradient, + }); @override void paint(PaintingContext context, Offset offset) { diff --git a/mobile/lib/presentation/widgets/images/thumbnail_tile.widget.dart b/mobile/lib/presentation/widgets/images/thumbnail_tile.widget.dart index 406ca30820..4803e26b86 100644 --- a/mobile/lib/presentation/widgets/images/thumbnail_tile.widget.dart +++ b/mobile/lib/presentation/widgets/images/thumbnail_tile.widget.dart @@ -2,15 +2,14 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/domain/models/setting.model.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/duration_extensions.dart'; import 'package:immich_mobile/extensions/theme_extensions.dart'; -import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'; import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart'; import 'package:immich_mobile/presentation/widgets/timeline/constants.dart'; +import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'; import 'package:immich_mobile/providers/backup/asset_upload_progress.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/setting.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; class ThumbnailTile extends ConsumerStatefulWidget { @@ -61,7 +60,7 @@ class _ThumbnailTileState extends ConsumerState { ); final bool storageIndicator = - ref.watch(settingsProvider.select((s) => s.get(Setting.showStorageIndicator))) && widget.showStorageIndicator; + ref.watch(appConfigProvider.select((s) => s.timeline.storageIndicator)) && widget.showStorageIndicator; if (!isCurrentAsset) { _hideIndicators = false; @@ -121,6 +120,9 @@ class _ThumbnailTileState extends ConsumerState { }, flightShuttleBuilder: (context, animation, direction, from, to) { void animationStatusListener(AnimationStatus status) { + if (!mounted) { + return; + } final heroInFlight = status == AnimationStatus.forward || status == AnimationStatus.reverse; if (_hideIndicators != heroInFlight) { setState(() => _hideIndicators = heroInFlight); diff --git a/mobile/lib/presentation/widgets/map/map.state.dart b/mobile/lib/presentation/widgets/map/map.state.dart index bfd3011050..57ce619771 100644 --- a/mobile/lib/presentation/widgets/map/map.state.dart +++ b/mobile/lib/presentation/widgets/map/map.state.dart @@ -3,10 +3,9 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/events.model.dart'; import 'package:immich_mobile/domain/utils/event_stream.dart'; import 'package:immich_mobile/infrastructure/repositories/timeline.repository.dart'; -import 'package:immich_mobile/providers/app_settings.provider.dart'; import 'package:immich_mobile/providers/infrastructure/map.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; import 'package:immich_mobile/providers/map/map_state.provider.dart'; -import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:maplibre_gl/maplibre_gl.dart'; class MapState { @@ -81,38 +80,38 @@ class MapStateNotifier extends Notifier { } void switchFavoriteOnly(bool isFavoriteOnly) { - ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.mapShowFavoriteOnly, isFavoriteOnly); + ref.read(settingsProvider).write(.mapShowFavoriteOnly, isFavoriteOnly); state = state.copyWith(onlyFavorites: isFavoriteOnly); EventStream.shared.emit(const MapMarkerReloadEvent()); } void switchIncludeArchived(bool isIncludeArchived) { - ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.mapIncludeArchived, isIncludeArchived); + ref.read(settingsProvider).write(.mapIncludeArchived, isIncludeArchived); state = state.copyWith(includeArchived: isIncludeArchived); EventStream.shared.emit(const MapMarkerReloadEvent()); } void switchWithPartners(bool isWithPartners) { - ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.mapwithPartners, isWithPartners); + ref.read(settingsProvider).write(.mapWithPartners, isWithPartners); state = state.copyWith(withPartners: isWithPartners); EventStream.shared.emit(const MapMarkerReloadEvent()); } void setRelativeTime(int relativeDays) { - ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.mapRelativeDate, relativeDays); + ref.read(settingsProvider).write(.mapRelativeDate, relativeDays); state = state.copyWith(relativeDays: relativeDays); EventStream.shared.emit(const MapMarkerReloadEvent()); } @override MapState build() { - final appSettingsService = ref.read(appSettingsServiceProvider); + final mapConfig = ref.read(appConfigProvider.select((config) => config.map)); return MapState( - themeMode: ThemeMode.values[appSettingsService.getSetting(AppSettingsEnum.mapThemeMode)], - onlyFavorites: appSettingsService.getSetting(AppSettingsEnum.mapShowFavoriteOnly), - includeArchived: appSettingsService.getSetting(AppSettingsEnum.mapIncludeArchived), - withPartners: appSettingsService.getSetting(AppSettingsEnum.mapwithPartners), - relativeDays: appSettingsService.getSetting(AppSettingsEnum.mapRelativeDate), + themeMode: mapConfig.themeMode, + onlyFavorites: mapConfig.favoritesOnly, + includeArchived: mapConfig.includeArchived, + withPartners: mapConfig.withPartners, + relativeDays: mapConfig.relativeDays, bounds: LatLngBounds(northeast: const LatLng(0, 0), southwest: const LatLng(0, 0)), ); } diff --git a/mobile/lib/presentation/widgets/memory/memory_card.widget.dart b/mobile/lib/presentation/widgets/memory/memory_card.widget.dart index 3df9c8074e..2f7a616632 100644 --- a/mobile/lib/presentation/widgets/memory/memory_card.widget.dart +++ b/mobile/lib/presentation/widgets/memory/memory_card.widget.dart @@ -54,7 +54,9 @@ class DriftMemoryCard extends StatelessWidget { } } - if (asset.isImage) return FullImage(asset, fit: fit, size: const Size(double.infinity, double.infinity)); + if (asset.isImage) { + return FullImage(asset, fit: fit, size: const Size(double.infinity, double.infinity)); + } return Center( child: AspectRatio( diff --git a/mobile/lib/presentation/widgets/search/quick_date_picker.dart b/mobile/lib/presentation/widgets/search/quick_date_picker.dart index 09b1cee700..e8bf3c5a43 100644 --- a/mobile/lib/presentation/widgets/search/quick_date_picker.dart +++ b/mobile/lib/presentation/widgets/search/quick_date_picker.dart @@ -136,7 +136,9 @@ class QuickDatePicker extends HookWidget { menuStyle: MenuStyle(maximumSize: WidgetStateProperty.all(Size(size.width, size.height * 0.5))), dropdownMenuEntries: _recentYears.map((e) => DropdownMenuEntry(value: e, label: e.toString())).toList(), onSelected: (year) { - if (year == null) return; + if (year == null) { + return; + } onSelect(YearFilter(year)); }, ), @@ -179,7 +181,9 @@ class QuickDatePicker extends HookWidget { child: SingleChildScrollView( child: RadioGroup( onChanged: (value) { - if (value == null) return; + if (value == null) { + return; + } final _ = switch (value) { _QuickPickerType.custom => onRequestPicker(), _QuickPickerType.last1Month => onSelect(RecentMonthRangeFilter(1)), diff --git a/mobile/lib/presentation/widgets/timeline/fixed/row.dart b/mobile/lib/presentation/widgets/timeline/fixed/row.dart index 97067add24..851cb5ebae 100644 --- a/mobile/lib/presentation/widgets/timeline/fixed/row.dart +++ b/mobile/lib/presentation/widgets/timeline/fixed/row.dart @@ -62,14 +62,11 @@ class RenderFixedRow extends RenderBox RenderBoxContainerDefaultsMixin { RenderFixedRow({ List? children, - required double height, - required List widths, - required double spacing, - required TextDirection textDirection, - }) : _height = height, - _widths = widths, - _spacing = spacing, - _textDirection = textDirection { + required this._height, + required this._widths, + required this._spacing, + required this._textDirection, + }) { addAll(children); } @@ -77,7 +74,9 @@ class RenderFixedRow extends RenderBox double _height; set height(double value) { - if (_height == value) return; + if (_height == value) { + return; + } _height = value; markNeedsLayout(); } @@ -86,7 +85,9 @@ class RenderFixedRow extends RenderBox List _widths; set widths(List value) { - if (listEquals(_widths, value)) return; + if (listEquals(_widths, value)) { + return; + } _widths = value; markNeedsLayout(); } @@ -95,7 +96,9 @@ class RenderFixedRow extends RenderBox double _spacing; set spacing(double value) { - if (_spacing == value) return; + if (_spacing == value) { + return; + } _spacing = value; markNeedsLayout(); } @@ -104,7 +107,9 @@ class RenderFixedRow extends RenderBox TextDirection _textDirection; set textDirection(TextDirection value) { - if (_textDirection == value) return; + if (_textDirection == value) { + return; + } _textDirection = value; markNeedsLayout(); } diff --git a/mobile/lib/presentation/widgets/timeline/fixed/segment.model.dart b/mobile/lib/presentation/widgets/timeline/fixed/segment.model.dart index c62a4946c7..ea6f3e739e 100644 --- a/mobile/lib/presentation/widgets/timeline/fixed/segment.model.dart +++ b/mobile/lib/presentation/widgets/timeline/fixed/segment.model.dart @@ -53,14 +53,18 @@ class FixedSegment extends Segment { @override int getMinChildIndexForScrollOffset(double scrollOffset) { final adjustedOffset = scrollOffset - gridOffset; - if (!adjustedOffset.isFinite || adjustedOffset < 0) return firstIndex; + if (!adjustedOffset.isFinite || adjustedOffset < 0) { + return firstIndex; + } return gridIndex + (adjustedOffset / mainAxisExtend).floor(); } @override int getMaxChildIndexForScrollOffset(double scrollOffset) { final adjustedOffset = scrollOffset - gridOffset; - if (!adjustedOffset.isFinite || adjustedOffset < 0) return firstIndex; + if (!adjustedOffset.isFinite || adjustedOffset < 0) { + return firstIndex; + } return gridIndex + (adjustedOffset / mainAxisExtend).ceil() - 1; } @@ -162,8 +166,12 @@ class _FixedSegmentRow extends ConsumerWidget { // 0.5: width < mean - threshold // 1.5: width > mean + threshold final arConfiguration = aspectRatios.map((e) { - if (e - meanAspectRatio > 0.3) return 1.5; - if (e - meanAspectRatio < -0.3) return 0.5; + if (e - meanAspectRatio > 0.3) { + return 1.5; + } + if (e - meanAspectRatio < -0.3) { + return 0.5; + } return 1.0; }); @@ -234,7 +242,11 @@ class _AssetTileWidget extends ConsumerWidget { return false; } - return lockSelectionAssets.contains(asset); + // Iterate with `==` instead of `Set.contains` because `RemoteAsset.hashCode` + // includes `localId` while `==` does not โ€” so the same server asset can + // hash to a different bucket when its `localId` differs (e.g., album-fetched + // copy has localId=null, merged-timeline copy has it populated). + return lockSelectionAssets.any((a) => a == asset); } @override diff --git a/mobile/lib/presentation/widgets/timeline/scrubber.widget.dart b/mobile/lib/presentation/widgets/timeline/scrubber.widget.dart index f0dfef571c..5eda237ec4 100644 --- a/mobile/lib/presentation/widgets/timeline/scrubber.widget.dart +++ b/mobile/lib/presentation/widgets/timeline/scrubber.widget.dart @@ -104,7 +104,9 @@ class ScrubberState extends ConsumerState with TickerProviderStateMixi late ScrollController _scrollController; double get _currentOffset { - if (_scrollController.hasClients != true) return 0.0; + if (_scrollController.hasClients != true) { + return 0.0; + } return _scrollController.offset * _scrubberHeight / _scrollController.position.maxScrollExtent; } @@ -576,9 +578,7 @@ class _SlideFadeTransition extends StatelessWidget { final Animation _animation; final Widget _child; - const _SlideFadeTransition({required Animation animation, required Widget child}) - : _animation = animation, - _child = child; + const _SlideFadeTransition({required this._animation, required this._child}); @override Widget build(BuildContext context) { diff --git a/mobile/lib/presentation/widgets/timeline/segment.model.dart b/mobile/lib/presentation/widgets/timeline/segment.model.dart index bc5f974874..99bf7b0a4d 100644 --- a/mobile/lib/presentation/widgets/timeline/segment.model.dart +++ b/mobile/lib/presentation/widgets/timeline/segment.model.dart @@ -52,7 +52,9 @@ abstract class Segment { @override bool operator ==(Object other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } return other is Segment && other.firstIndex == firstIndex && diff --git a/mobile/lib/presentation/widgets/timeline/timeline.state.dart b/mobile/lib/presentation/widgets/timeline/timeline.state.dart index 1e1d4130f7..8dd87f9868 100644 --- a/mobile/lib/presentation/widgets/timeline/timeline.state.dart +++ b/mobile/lib/presentation/widgets/timeline/timeline.state.dart @@ -1,12 +1,11 @@ import 'dart:math' as math; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/setting.model.dart'; import 'package:immich_mobile/domain/models/timeline.model.dart'; import 'package:immich_mobile/presentation/widgets/timeline/constants.dart'; import 'package:immich_mobile/presentation/widgets/timeline/fixed/segment_builder.dart'; import 'package:immich_mobile/presentation/widgets/timeline/segment.model.dart'; -import 'package:immich_mobile/providers/infrastructure/setting.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; class TimelineArgs { @@ -93,7 +92,7 @@ final timelineSegmentProvider = StreamProvider.autoDispose>((ref) final availableTileWidth = args.maxWidth - (spacing * (columnCount - 1)); final tileExtent = math.max(0, availableTileWidth) / columnCount; - final groupBy = args.groupBy ?? GroupAssetsBy.values[ref.watch(settingsProvider).get(Setting.groupAssetsBy)]; + final groupBy = args.groupBy ?? ref.watch(appConfigProvider.select((config) => config.timeline.groupAssetsBy)); final timelineService = ref.watch(timelineServiceProvider); yield* timelineService.watchBuckets().map((buckets) { @@ -102,7 +101,7 @@ final timelineSegmentProvider = StreamProvider.autoDispose>((ref) tileHeight: tileExtent, columnCount: columnCount, spacing: spacing, - groupBy: groupBy, + groupBy: groupBy!, ).generate(); }); }, dependencies: [timelineServiceProvider, timelineArgsProvider]); diff --git a/mobile/lib/presentation/widgets/timeline/timeline.widget.dart b/mobile/lib/presentation/widgets/timeline/timeline.widget.dart index 578bd37a23..eb7a31ac8b 100644 --- a/mobile/lib/presentation/widgets/timeline/timeline.widget.dart +++ b/mobile/lib/presentation/widgets/timeline/timeline.widget.dart @@ -10,7 +10,6 @@ import 'package:flutter/rendering.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/events.model.dart'; -import 'package:immich_mobile/domain/models/setting.model.dart'; import 'package:immich_mobile/domain/models/timeline.model.dart'; import 'package:immich_mobile/domain/utils/event_stream.dart'; import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; @@ -22,8 +21,8 @@ import 'package:immich_mobile/presentation/widgets/timeline/scrubber.widget.dart import 'package:immich_mobile/presentation/widgets/timeline/segment.model.dart'; import 'package:immich_mobile/presentation/widgets/timeline/timeline.state.dart'; import 'package:immich_mobile/presentation/widgets/timeline/timeline_drag_region.dart'; +import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/setting.provider.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; import 'package:immich_mobile/widgets/common/immich_sliver_app_bar.dart'; @@ -64,36 +63,32 @@ class Timeline extends StatelessWidget { @override Widget build(BuildContext context) { - return Scaffold( - resizeToAvoidBottomInset: false, - floatingActionButton: const DownloadStatusFloatingButton(), - body: LayoutBuilder( - builder: (_, constraints) => ProviderScope( - overrides: [ - timelineArgsProvider.overrideWith( - (ref) => TimelineArgs( - maxWidth: constraints.maxWidth, - maxHeight: constraints.maxHeight, - columnCount: ref.watch(settingsProvider.select((s) => s.get(Setting.tilesPerRow))), - showStorageIndicator: showStorageIndicator, - withStack: withStack, - groupBy: groupBy, - ), + return LayoutBuilder( + builder: (_, constraints) => ProviderScope( + overrides: [ + timelineArgsProvider.overrideWith( + (ref) => TimelineArgs( + maxWidth: constraints.maxWidth, + maxHeight: constraints.maxHeight, + columnCount: ref.watch(appConfigProvider.select((config) => config.timeline.tilesPerRow)), + showStorageIndicator: showStorageIndicator, + withStack: withStack, + groupBy: groupBy, ), - if (readOnly) readonlyModeProvider.overrideWith(() => _AlwaysReadOnlyNotifier()), - ], - child: _SliverTimeline( - topSliverWidget: topSliverWidget, - topSliverWidgetHeight: topSliverWidgetHeight, - bottomSliverWidget: bottomSliverWidget, - appBar: appBar, - bottomSheet: bottomSheet, - withScrubber: withScrubber, - persistentBottomBar: persistentBottomBar, - snapToMonth: snapToMonth, - maxWidth: constraints.maxWidth, - loadingWidget: loadingWidget, ), + if (readOnly) readonlyModeProvider.overrideWith(() => _AlwaysReadOnlyNotifier()), + ], + child: _SliverTimeline( + topSliverWidget: topSliverWidget, + topSliverWidgetHeight: topSliverWidgetHeight, + bottomSliverWidget: bottomSliverWidget, + appBar: appBar, + bottomSheet: bottomSheet, + withScrubber: withScrubber, + persistentBottomBar: persistentBottomBar, + snapToMonth: snapToMonth, + maxWidth: constraints.maxWidth, + loadingWidget: loadingWidget, ), ), ); @@ -161,7 +156,7 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> { _scrollController = ScrollController(onAttach: _restoreAssetPosition); _eventSubscription = EventStream.shared.listen(_onEvent); - final currentTilesPerRow = ref.read(settingsProvider).get(Setting.tilesPerRow); + final currentTilesPerRow = ref.read(appConfigProvider.select((config) => config.timeline.tilesPerRow)); _perRow = currentTilesPerRow; _scaleFactor = 7.0 - _perRow; _baseScaleFactor = _scaleFactor; @@ -201,7 +196,9 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> { } void _restoreAssetPosition(_) { - if (_restoreAssetIndex == null) return; + if (_restoreAssetIndex == null) { + return; + } final asyncSegments = ref.read(timelineSegmentProvider); asyncSegments.whenData((segments) { @@ -329,7 +326,9 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> { } void _handleDragAssetEnter(TimelineAssetIndex index) { - if (_dragAnchorIndex == null || !_dragging) return; + if (_dragAnchorIndex == null || !_dragging) { + return; + } final timelineService = ref.read(timelineServiceProvider); final dragAnchorIndex = _dragAnchorIndex!; @@ -375,119 +374,126 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> { ref.read(multiSelectProvider.notifier).reset(); } }, - child: asyncSegments.widgetWhen( - onLoading: widget.loadingWidget != null ? () => widget.loadingWidget! : null, - onData: (segments) { - final childCount = (segments.lastOrNull?.lastIndex ?? -1) + 1; - final double appBarExpandedHeight = widget.appBar != null && widget.appBar is MesmerizingSliverAppBar - ? 200 - : 0; - final topPadding = context.padding.top + (widget.appBar == null ? 0 : kToolbarHeight) + 10; + child: PrimaryScrollController( + controller: _scrollController, + child: Scaffold( + resizeToAvoidBottomInset: false, + floatingActionButton: const DownloadStatusFloatingButton(), + body: asyncSegments.widgetWhen( + onLoading: widget.loadingWidget != null ? () => widget.loadingWidget! : null, + onData: (segments) { + final childCount = (segments.lastOrNull?.lastIndex ?? -1) + 1; + final double appBarExpandedHeight = widget.appBar != null && widget.appBar is MesmerizingSliverAppBar + ? 200 + : 0; + final topPadding = context.padding.top + (widget.appBar == null ? 0 : kToolbarHeight) + 10; - const bottomSheetOpenModifier = 120.0; - final contentBottomPadding = context.padding.bottom + (isMultiSelectEnabled ? bottomSheetOpenModifier : 0); - final scrubberBottomPadding = contentBottomPadding + kScrubberThumbHeight; + const bottomSheetOpenModifier = 120.0; + final contentBottomPadding = + context.padding.bottom + (isMultiSelectEnabled ? bottomSheetOpenModifier : 0); + final scrubberBottomPadding = contentBottomPadding + kScrubberThumbHeight; - final grid = CustomScrollView( - primary: true, - physics: _scrollPhysics, - cacheExtent: maxHeight * 2, - slivers: [ - if (isSelectionMode) const SelectionSliverAppBar() else if (widget.appBar != null) widget.appBar!, - if (widget.topSliverWidget != null) widget.topSliverWidget!, - _SliverSegmentedList( - segments: segments, - delegate: SliverChildBuilderDelegate( - (ctx, index) { - if (index >= childCount) return null; - final segment = segments.findByIndex(index); - return segment?.builder(ctx, index) ?? const SizedBox.shrink(); - }, - childCount: childCount, - addAutomaticKeepAlives: false, - // We add repaint boundary around tiles, so skip the auto boundaries - addRepaintBoundaries: false, - ), - ), - if (widget.bottomSliverWidget != null) widget.bottomSliverWidget!, - SliverPadding(padding: EdgeInsets.only(bottom: contentBottomPadding)), - ], - ); + final grid = CustomScrollView( + primary: true, + physics: _scrollPhysics, + scrollCacheExtent: .pixels(maxHeight * 2), + slivers: [ + if (isSelectionMode) const SelectionSliverAppBar() else if (widget.appBar != null) widget.appBar!, + if (widget.topSliverWidget != null) widget.topSliverWidget!, + _SliverSegmentedList( + segments: segments, + delegate: SliverChildBuilderDelegate( + (ctx, index) { + if (index >= childCount) { + return null; + } + final segment = segments.findByIndex(index); + return segment?.builder(ctx, index) ?? const SizedBox.shrink(); + }, + childCount: childCount, + addAutomaticKeepAlives: false, + // We add repaint boundary around tiles, so skip the auto boundaries + addRepaintBoundaries: false, + ), + ), + if (widget.bottomSliverWidget != null) widget.bottomSliverWidget!, + SliverPadding(padding: EdgeInsets.only(bottom: contentBottomPadding)), + ], + ); - final Widget timeline; - if (widget.withScrubber) { - timeline = Scrubber( - snapToMonth: widget.snapToMonth, - layoutSegments: segments, - timelineHeight: maxHeight, - topPadding: topPadding, - bottomPadding: scrubberBottomPadding, - monthSegmentSnappingOffset: widget.topSliverWidgetHeight ?? 0 + appBarExpandedHeight, - hasAppBar: widget.appBar != null, - child: grid, - ); - } else { - timeline = grid; - } + final Widget timeline; + if (widget.withScrubber) { + timeline = Scrubber( + snapToMonth: widget.snapToMonth, + layoutSegments: segments, + timelineHeight: maxHeight, + topPadding: topPadding, + bottomPadding: scrubberBottomPadding, + monthSegmentSnappingOffset: widget.topSliverWidgetHeight ?? 0 + appBarExpandedHeight, + hasAppBar: widget.appBar != null, + child: grid, + ); + } else { + timeline = grid; + } - return PrimaryScrollController( - controller: _scrollController, - child: RawGestureDetector( - gestures: { - CustomScaleGestureRecognizer: GestureRecognizerFactoryWithHandlers( - () => CustomScaleGestureRecognizer(), - (CustomScaleGestureRecognizer scale) { - scale.onStart = (details) { - _baseScaleFactor = _scaleFactor; - }; + return RawGestureDetector( + gestures: { + CustomScaleGestureRecognizer: GestureRecognizerFactoryWithHandlers( + () => CustomScaleGestureRecognizer(), + (CustomScaleGestureRecognizer scale) { + scale.onStart = (details) { + _baseScaleFactor = _scaleFactor; + }; - scale.onUpdate = (details) { - final newScaleFactor = math.max(math.min(5.0, _baseScaleFactor * details.scale), 1.0); - final newPerRow = 7 - newScaleFactor.toInt(); + scale.onUpdate = (details) { + final newScaleFactor = math.max(math.min(5.0, _baseScaleFactor * details.scale), 1.0); + final newPerRow = 7 - newScaleFactor.toInt(); - if (newPerRow != _perRow) { - final targetAssetIndex = _getCurrentAssetIndex(segments); - setState(() { - _scaleFactor = newScaleFactor; - _perRow = newPerRow; - _restoreAssetIndex = targetAssetIndex; - }); + if (newPerRow != _perRow) { + final targetAssetIndex = _getCurrentAssetIndex(segments); + setState(() { + _scaleFactor = newScaleFactor; + _perRow = newPerRow; + _restoreAssetIndex = targetAssetIndex; + }); - ref.read(settingsProvider.notifier).set(Setting.tilesPerRow, _perRow); - } - }; - }, - ), - }, - child: TimelineDragRegion( - onStart: !isReadonlyModeEnabled ? _setDragStartIndex : null, - onAssetEnter: _handleDragAssetEnter, - onEnd: !isReadonlyModeEnabled ? _stopDrag : null, - onScroll: _dragScroll, - onScrollStart: () { - // Minimize the bottom sheet when drag selection starts - ref.read(timelineStateProvider.notifier).setScrolling(true); + ref.read(settingsProvider).write(.timelineTilesPerRow, _perRow); + } + }; + }, + ), }, - child: Stack( - clipBehavior: Clip.none, - children: [ - timeline, - if (isBottomWidgetVisible) - Positioned( - top: MediaQuery.paddingOf(context).top, - left: 25, - child: const SizedBox( - height: kToolbarHeight, - child: Center(child: _MultiSelectStatusButton()), + child: TimelineDragRegion( + onStart: !isReadonlyModeEnabled ? _setDragStartIndex : null, + onAssetEnter: _handleDragAssetEnter, + onEnd: !isReadonlyModeEnabled ? _stopDrag : null, + onScroll: _dragScroll, + onScrollStart: () { + // Minimize the bottom sheet when drag selection starts + ref.read(timelineStateProvider.notifier).setScrolling(true); + }, + child: Stack( + clipBehavior: Clip.none, + children: [ + timeline, + if (isBottomWidgetVisible) + Positioned( + top: MediaQuery.paddingOf(context).top, + left: 25, + child: const SizedBox( + height: kToolbarHeight, + child: Center(child: _MultiSelectStatusButton()), + ), ), - ), - if (isBottomWidgetVisible) widget.bottomSheet!, - ], + if (isBottomWidgetVisible) widget.bottomSheet!, + ], + ), ), - ), - ), - ); - }, + ); + }, + ), + ), ), ); } @@ -496,7 +502,7 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> { class _SliverSegmentedList extends SliverMultiBoxAdaptorWidget { final List _segments; - const _SliverSegmentedList({required List segments, required super.delegate}) : _segments = segments; + const _SliverSegmentedList({required this._segments, required super.delegate}); @override _RenderSliverTimelineBoxAdaptor createRenderObject(BuildContext context) => @@ -520,8 +526,7 @@ class _RenderSliverTimelineBoxAdaptor extends RenderSliverMultiBoxAdaptor { markNeedsLayout(); } - _RenderSliverTimelineBoxAdaptor({required super.childManager, required List segments}) - : _segments = segments; + _RenderSliverTimelineBoxAdaptor({required super.childManager, required this._segments}); int getMinChildIndexForScrollOffset(double offset) => _segments.findByOffset(offset)?.getMinChildIndexForScrollOffset(offset) ?? 0; diff --git a/mobile/lib/presentation/widgets/timeline/timeline_drag_region.dart b/mobile/lib/presentation/widgets/timeline/timeline_drag_region.dart index 88d46b143f..9ffcc3b23b 100644 --- a/mobile/lib/presentation/widgets/timeline/timeline_drag_region.dart +++ b/mobile/lib/presentation/widgets/timeline/timeline_drag_region.dart @@ -81,11 +81,15 @@ class _TimelineDragRegionState extends State { TimelineAssetIndex? _getValueKeyAtPosition(Offset position) { final box = context.findAncestorRenderObjectOfType(); - if (box == null) return null; + if (box == null) { + return null; + } final hitTestResult = BoxHitTestResult(); final local = box.globalToLocal(position); - if (!box.hitTest(hitTestResult, position: local)) return null; + if (!box.hitTest(hitTestResult, position: local)) { + return null; + } return (hitTestResult.path.firstWhereOrNull((hit) => hit.target is _TimelineAssetIndexProxy)?.target as _TimelineAssetIndexProxy?) @@ -103,7 +107,9 @@ class _TimelineDragRegionState extends State { final initialHit = _getValueKeyAtPosition(event.globalPosition); anchorAsset = initialHit; - if (initialHit == null) return; + if (initialHit == null) { + return; + } if (anchorAsset != null) { widget.onStart?.call(anchorAsset!); @@ -117,8 +123,12 @@ class _TimelineDragRegionState extends State { } void _onLongPressMove(LongPressMoveUpdateDetails event) { - if (anchorAsset == null) return; - if (topScrollOffset == null || bottomScrollOffset == null) return; + if (anchorAsset == null) { + return; + } + if (topScrollOffset == null || bottomScrollOffset == null) { + return; + } final currentDy = event.localPosition.dy; @@ -138,7 +148,9 @@ class _TimelineDragRegionState extends State { } final currentlyTouchingAsset = _getValueKeyAtPosition(event.globalPosition); - if (currentlyTouchingAsset == null) return; + if (currentlyTouchingAsset == null) { + return; + } if (assetUnderPointer != currentlyTouchingAsset) { if (!scrollNotified) { @@ -202,7 +214,9 @@ class TimelineAssetIndex { @override bool operator ==(covariant TimelineAssetIndex other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } return other.assetIndex == assetIndex && other.segmentIndex == segmentIndex; } diff --git a/mobile/lib/providers/album/pending_album_uploads.provider.dart b/mobile/lib/providers/album/pending_album_uploads.provider.dart new file mode 100644 index 0000000000..7b1188891b --- /dev/null +++ b/mobile/lib/providers/album/pending_album_uploads.provider.dart @@ -0,0 +1,86 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; + +class PendingAlbumUpload { + final LocalAsset asset; + final double progress; + final bool failed; + + const PendingAlbumUpload({required this.asset, this.progress = 0.0, this.failed = false}); + + PendingAlbumUpload copyWith({double? progress, bool? failed}) => + PendingAlbumUpload(asset: asset, progress: progress ?? this.progress, failed: failed ?? this.failed); +} + +class AlbumPendingUploadsNotifier extends AutoDisposeFamilyNotifier, String> { + KeepAliveLink? _keepAliveLink; + + @override + List build(String albumId) { + ref.onDispose(() { + _keepAliveLink?.close(); + _keepAliveLink = null; + }); + + return const []; + } + + void enqueue(Iterable assets) { + if (assets.isEmpty) { + return; + } + + final existingIds = state.map((e) => e.asset.id).toSet(); + final additions = assets.where((a) => !existingIds.contains(a.id)).map((a) => PendingAlbumUpload(asset: a)); + state = [...state, ...additions]; + _syncKeepAlive(); + } + + void updateProgress(String localAssetId, double progress) { + state = [ + for (final entry in state) + if (entry.asset.id == localAssetId) entry.copyWith(progress: progress, failed: false) else entry, + ]; + _syncKeepAlive(); + } + + void markFailed(String localAssetId) { + state = [ + for (final entry in state) + if (entry.asset.id == localAssetId) entry.copyWith(failed: true) else entry, + ]; + _syncKeepAlive(); + } + + void markAllFailed() { + state = [for (final entry in state) entry.copyWith(failed: true)]; + _syncKeepAlive(); + } + + void remove(String localAssetId) { + state = state.where((e) => e.asset.id != localAssetId).toList(); + _syncKeepAlive(); + } + + void clearFailed() { + state = state.where((e) => !e.failed).toList(); + _syncKeepAlive(); + } + + void clear() { + state = const []; + _syncKeepAlive(); + } + + void _syncKeepAlive() { + if (state.isEmpty) { + _keepAliveLink?.close(); + _keepAliveLink = null; + } else { + _keepAliveLink ??= ref.keepAlive(); + } + } +} + +final pendingAlbumUploadsProvider = NotifierProvider.autoDispose + .family, String>(AlbumPendingUploadsNotifier.new); diff --git a/mobile/lib/providers/app_life_cycle.provider.dart b/mobile/lib/providers/app_life_cycle.provider.dart index a5f67215a8..5cd294d781 100644 --- a/mobile/lib/providers/app_life_cycle.provider.dart +++ b/mobile/lib/providers/app_life_cycle.provider.dart @@ -5,16 +5,15 @@ import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/services/log.service.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/platform_extensions.dart'; -import 'package:immich_mobile/providers/app_settings.provider.dart'; import 'package:immich_mobile/providers/auth.provider.dart'; import 'package:immich_mobile/providers/background_sync.provider.dart'; import 'package:immich_mobile/providers/backup/drift_backup.provider.dart'; import 'package:immich_mobile/providers/gallery_permission.provider.dart'; import 'package:immich_mobile/providers/infrastructure/platform.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; import 'package:immich_mobile/providers/notification_permission.provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; import 'package:immich_mobile/providers/websocket.provider.dart'; -import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:logging/logging.dart'; enum AppLifeCycleEnum { active, inactive, paused, resumed, detached, hidden } @@ -65,7 +64,9 @@ class AppLifeCycleNotifier extends StateNotifier { Future _performResume() async { // no need to resume because app was never really paused - if (!_wasPaused) return; + if (!_wasPaused) { + return; + } _wasPaused = false; final isAuthenticated = _ref.read(authProvider).isAuthenticated; @@ -106,7 +107,7 @@ class AppLifeCycleNotifier extends StateNotifier { await Future.delayed(const Duration(milliseconds: 500)); final backgroundManager = _ref.read(backgroundSyncProvider); - final isAlbumLinkedSyncEnable = _ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.syncAlbums); + final isAlbumLinkedSyncEnable = _ref.read(appConfigProvider).backup.syncAlbums; try { bool syncSuccess = false; @@ -136,7 +137,7 @@ class AppLifeCycleNotifier extends StateNotifier { } Future _resumeBackup() async { - final isEnableBackup = _ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.enableBackup); + final isEnableBackup = _ref.read(appConfigProvider).backup.enabled; if (isEnableBackup) { final currentUser = Store.tryGet(StoreKey.currentUser); diff --git a/mobile/lib/providers/asset_viewer/asset_viewer.provider.dart b/mobile/lib/providers/asset_viewer/asset_viewer.provider.dart index a38bbba232..5c9f3c92a3 100644 --- a/mobile/lib/providers/asset_viewer/asset_viewer.provider.dart +++ b/mobile/lib/providers/asset_viewer/asset_viewer.provider.dart @@ -49,8 +49,12 @@ class AssetViewerState { @override bool operator ==(Object other) { - if (identical(this, other)) return true; - if (other.runtimeType != runtimeType) return false; + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } return other is AssetViewerState && other.backgroundOpacity == backgroundOpacity && other.showingDetails == showingDetails && @@ -89,7 +93,9 @@ class AssetViewerStateNotifier extends Notifier { } void setAsset(BaseAsset asset) { - if (asset == state.currentAsset) return; + if (asset == state.currentAsset) { + return; + } state = state.copyWith(currentAsset: asset, stackIndex: 0, showingOcr: false); } @@ -148,6 +154,8 @@ final assetViewerProvider = NotifierProvider((ref) { ref.watch(assetViewerProvider.select((s) => s.currentAsset?.heroTag)); final asset = ref.read(assetViewerProvider).currentAsset; - if (asset == null) return const Stream.empty(); + if (asset == null) { + return const Stream.empty(); + } return ref.read(assetServiceProvider).watchAsset(asset); }); diff --git a/mobile/lib/providers/asset_viewer/share_intent_upload.provider.dart b/mobile/lib/providers/asset_viewer/share_intent_upload.provider.dart index 66a8deb466..8bd0581061 100644 --- a/mobile/lib/providers/asset_viewer/share_intent_upload.provider.dart +++ b/mobile/lib/providers/asset_viewer/share_intent_upload.provider.dart @@ -1,101 +1,101 @@ -import 'dart:io'; - -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/models/upload/share_intent_attachment.model.dart'; -import 'package:immich_mobile/routing/router.dart'; -import 'package:immich_mobile/services/share_intent_service.dart'; -import 'package:immich_mobile/services/foreground_upload.service.dart'; -import 'package:logging/logging.dart'; -import 'package:path/path.dart' as p; - -final shareIntentUploadProvider = StateNotifierProvider>( - ((ref) => ShareIntentUploadStateNotifier( - ref.watch(appRouterProvider), - ref.read(foregroundUploadServiceProvider), - ref.read(shareIntentServiceProvider), - )), -); - -class ShareIntentUploadStateNotifier extends StateNotifier> { - final AppRouter router; - final ForegroundUploadService _foregroundUploadService; - final ShareIntentService _shareIntentService; - final Logger _logger = Logger('ShareIntentUploadStateNotifier'); - - ShareIntentUploadStateNotifier(this.router, this._foregroundUploadService, this._shareIntentService) : super([]); - - void init() { - _shareIntentService.onSharedMedia = onSharedMedia; - _shareIntentService.init(); - } - - void onSharedMedia(List attachments) { - router.removeWhere((route) => route.name == "ShareIntentRoute"); - clearAttachments(); - addAttachments(attachments); - router.push(ShareIntentRoute(attachments: attachments)); - } - - void addAttachments(List attachments) { - if (attachments.isEmpty) { - return; - } - state = [...state, ...attachments]; - } - - void removeAttachment(ShareIntentAttachment attachment) { - final updatedState = state.where((element) => element != attachment).toList(); - if (updatedState.length != state.length) { - state = updatedState; - } - } - - void clearAttachments() { - if (state.isEmpty) { - return; - } - - state = []; - } - - Future uploadAll(List files) async { - for (final file in files) { - final fileId = p.hash(file.path).toString(); - _updateStatus(fileId, UploadStatus.running); - } - - await _foregroundUploadService.uploadShareIntent( - files, - onProgress: (fileId, bytes, totalBytes) { - final progress = totalBytes > 0 ? bytes / totalBytes : 0.0; - _updateProgress(fileId, progress); - }, - onSuccess: (fileId) { - _updateStatus(fileId, UploadStatus.complete, progress: 1.0); - }, - onError: (fileId, errorMessage) { - _logger.warning("Upload failed for file: $fileId, error: $errorMessage"); - _updateStatus(fileId, UploadStatus.failed); - }, - ); - } - - void _updateStatus(String fileId, UploadStatus status, {double? progress}) { - final id = int.parse(fileId); - state = [ - for (final attachment in state) - if (attachment.id == id) - attachment.copyWith(status: status, uploadProgress: progress ?? attachment.uploadProgress) - else - attachment, - ]; - } - - void _updateProgress(String fileId, double progress) { - final id = int.parse(fileId); - state = [ - for (final attachment in state) - if (attachment.id == id) attachment.copyWith(uploadProgress: progress) else attachment, - ]; - } -} +import 'dart:io'; + +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/models/upload/share_intent_attachment.model.dart'; +import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/services/foreground_upload.service.dart'; +import 'package:immich_mobile/services/share_intent_service.dart'; +import 'package:logging/logging.dart'; +import 'package:path/path.dart' as p; + +final shareIntentUploadProvider = StateNotifierProvider>( + ((ref) => ShareIntentUploadStateNotifier( + ref.watch(appRouterProvider), + ref.read(foregroundUploadServiceProvider), + ref.read(shareIntentServiceProvider), + )), +); + +class ShareIntentUploadStateNotifier extends StateNotifier> { + final AppRouter router; + final ForegroundUploadService _foregroundUploadService; + final ShareIntentService _shareIntentService; + final Logger _logger = Logger('ShareIntentUploadStateNotifier'); + + ShareIntentUploadStateNotifier(this.router, this._foregroundUploadService, this._shareIntentService) : super([]); + + void init() { + _shareIntentService.onSharedMedia = onSharedMedia; + _shareIntentService.init(); + } + + void onSharedMedia(List attachments) { + router.removeWhere((route) => route.name == "ShareIntentRoute"); + clearAttachments(); + addAttachments(attachments); + router.push(ShareIntentRoute(attachments: attachments)); + } + + void addAttachments(List attachments) { + if (attachments.isEmpty) { + return; + } + state = [...state, ...attachments]; + } + + void removeAttachment(ShareIntentAttachment attachment) { + final updatedState = state.where((element) => element != attachment).toList(); + if (updatedState.length != state.length) { + state = updatedState; + } + } + + void clearAttachments() { + if (state.isEmpty) { + return; + } + + state = []; + } + + Future uploadAll(List files) async { + for (final file in files) { + final fileId = p.hash(file.path).toString(); + _updateStatus(fileId, UploadStatus.running); + } + + await _foregroundUploadService.uploadShareIntent( + files, + onProgress: (fileId, bytes, totalBytes) { + final progress = totalBytes > 0 ? bytes / totalBytes : 0.0; + _updateProgress(fileId, progress); + }, + onSuccess: (fileId, _) { + _updateStatus(fileId, UploadStatus.complete, progress: 1.0); + }, + onError: (fileId, errorMessage) { + _logger.warning("Upload failed for file: $fileId, error: $errorMessage"); + _updateStatus(fileId, UploadStatus.failed); + }, + ); + } + + void _updateStatus(String fileId, UploadStatus status, {double? progress}) { + final id = int.parse(fileId); + state = [ + for (final attachment in state) + if (attachment.id == id) + attachment.copyWith(status: status, uploadProgress: progress ?? attachment.uploadProgress) + else + attachment, + ]; + } + + void _updateProgress(String fileId, double progress) { + final id = int.parse(fileId); + state = [ + for (final attachment in state) + if (attachment.id == id) attachment.copyWith(uploadProgress: progress) else attachment, + ]; + } +} diff --git a/mobile/lib/providers/asset_viewer/video_player_provider.dart b/mobile/lib/providers/asset_viewer/video_player_provider.dart index 8093926873..463a1ac3d2 100644 --- a/mobile/lib/providers/asset_viewer/video_player_provider.dart +++ b/mobile/lib/providers/asset_viewer/video_player_provider.dart @@ -70,7 +70,9 @@ class VideoPlayerNotifier extends StateNotifier { } Future pause() async { - if (_controller == null) return; + if (_controller == null) { + return; + } _bufferingTimer?.cancel(); @@ -83,7 +85,9 @@ class VideoPlayerNotifier extends StateNotifier { } Future play() async { - if (_controller == null) return; + if (_controller == null) { + return; + } try { await _flushSeek(); @@ -97,18 +101,24 @@ class VideoPlayerNotifier extends StateNotifier { Future _flushSeek() async { final timer = _seekTimer; - if (timer == null || !timer.isActive) return; + if (timer == null || !timer.isActive) { + return; + } timer.cancel(); await _controller?.seekTo(state.position.inMilliseconds); } void seekTo(Duration position) { - if (_controller == null || state.position == position) return; + if (_controller == null || state.position == position) { + return; + } state = state.copyWith(position: position); - if (_seekTimer?.isActive ?? false) return; + if (_seekTimer?.isActive ?? false) { + return; + } _seekTimer = Timer(const Duration(milliseconds: 150), () { _controller?.seekTo(state.position.inMilliseconds); @@ -130,7 +140,9 @@ class VideoPlayerNotifier extends StateNotifier { /// Pauses playback and preserves the current status for later restoration. void hold() { - if (_holdStatus != null) return; + if (_holdStatus != null) { + return; + } _holdStatus = state.status; pause(); @@ -170,12 +182,16 @@ class VideoPlayerNotifier extends StateNotifier { } void onNativePlaybackReady() { - if (!mounted) return; + if (!mounted) { + return; + } final playbackInfo = _controller?.playbackInfo; final videoInfo = _controller?.videoInfo; - if (playbackInfo == null || videoInfo == null) return; + if (playbackInfo == null || videoInfo == null) { + return; + } state = state.copyWith( position: Duration(milliseconds: playbackInfo.position), @@ -185,15 +201,23 @@ class VideoPlayerNotifier extends StateNotifier { } void onNativePositionChanged() { - if (!mounted || (_seekTimer?.isActive ?? false)) return; + if (!mounted || (_seekTimer?.isActive ?? false)) { + return; + } final playbackInfo = _controller?.playbackInfo; - if (playbackInfo == null) return; + if (playbackInfo == null) { + return; + } final position = Duration(milliseconds: playbackInfo.position); - if (state.position == position) return; + if (state.position == position) { + return; + } - if (state.status == VideoPlaybackStatus.playing) _startBufferingTimer(); + if (state.status == VideoPlaybackStatus.playing) { + _startBufferingTimer(); + } state = state.copyWith( position: position, @@ -202,10 +226,14 @@ class VideoPlayerNotifier extends StateNotifier { } void onNativeStatusChanged() { - if (!mounted) return; + if (!mounted) { + return; + } final playbackInfo = _controller?.playbackInfo; - if (playbackInfo == null) return; + if (playbackInfo == null) { + return; + } final newStatus = _mapStatus(playbackInfo.status); switch (newStatus) { @@ -216,7 +244,9 @@ class VideoPlayerNotifier extends StateNotifier { onNativePlaybackEnded(); } - if (state.status != newStatus) state = state.copyWith(status: newStatus); + if (state.status != newStatus) { + state = state.copyWith(status: newStatus); + } } void onNativePlaybackEnded() { diff --git a/mobile/lib/providers/auth.provider.dart b/mobile/lib/providers/auth.provider.dart index 825d9e7bc8..8a3f503553 100644 --- a/mobile/lib/providers/auth.provider.dart +++ b/mobile/lib/providers/auth.provider.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:flutter_udid/flutter_udid.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/constants.dart'; @@ -8,15 +10,15 @@ import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/models/auth/auth_state.model.dart'; import 'package:immich_mobile/models/auth/login_response.model.dart'; import 'package:immich_mobile/providers/api.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; import 'package:immich_mobile/providers/infrastructure/user.provider.dart'; import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/services/auth.service.dart'; +import 'package:immich_mobile/services/background_upload.service.dart'; import 'package:immich_mobile/services/foreground_upload.service.dart'; import 'package:immich_mobile/services/secure_storage.service.dart'; -import 'package:immich_mobile/services/background_upload.service.dart'; import 'package:immich_mobile/services/widget.service.dart'; import 'package:immich_mobile/utils/debug_print.dart'; -import 'package:immich_mobile/utils/hash.dart'; import 'package:logging/logging.dart'; import 'package:openapi/api.dart'; @@ -127,7 +129,8 @@ class AuthNotifier extends StateNotifier { await _apiService.updateHeaders(); final serverEndpoint = Store.get(StoreKey.serverEndpoint); - final customHeaders = Store.tryGet(StoreKey.customHeaders); + final headerMap = _ref.read(appConfigProvider).network.customHeaders; + final customHeaders = headerMap.isEmpty ? null : jsonEncode(headerMap); await _widgetService.writeCredentials(serverEndpoint, accessToken, customHeaders); // Get the deviceid from the store if it exists, otherwise generate a new one @@ -144,7 +147,6 @@ class AuthNotifier extends StateNotifier { // Due to the flow of the code, this will always happen on first login user = serverUser; await Store.put(StoreKey.deviceId, deviceId); - await Store.put(StoreKey.deviceIdHash, fastHash(deviceId)); } } on ApiException catch (error, stackTrace) { if (error.code == 401) { @@ -176,19 +178,19 @@ class AuthNotifier extends StateNotifier { } Future saveWifiName(String wifiName) async { - await Store.put(StoreKey.preferredWifiName, wifiName); + await _ref.read(settingsProvider).write(.networkPreferredWifiName, wifiName); } Future saveLocalEndpoint(String url) async { - await Store.put(StoreKey.localEndpoint, url); + await _ref.read(settingsProvider).write(.networkLocalEndpoint, url); } String? getSavedWifiName() { - return Store.tryGet(StoreKey.preferredWifiName); + return _ref.read(appConfigProvider).network.preferredWifiName; } String? getSavedLocalEndpoint() { - return Store.tryGet(StoreKey.localEndpoint); + return _ref.read(appConfigProvider).network.localEndpoint; } /// Returns the current server endpoint (with /api) URL from the store diff --git a/mobile/lib/providers/backup/drift_backup.provider.dart b/mobile/lib/providers/backup/drift_backup.provider.dart index 4507747c7d..bf2b7cae4a 100644 --- a/mobile/lib/providers/backup/drift_backup.provider.dart +++ b/mobile/lib/providers/backup/drift_backup.provider.dart @@ -73,7 +73,9 @@ class DriftUploadStatus { @override bool operator ==(covariant DriftUploadStatus other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } return other.taskId == taskId && other.filename == filename && @@ -153,7 +155,9 @@ class DriftBackupState { @override bool operator ==(covariant DriftBackupState other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } final mapEquals = const DeepCollectionEquality().equals; return other.totalCount == totalCount && diff --git a/mobile/lib/providers/cleanup.provider.dart b/mobile/lib/providers/cleanup.provider.dart index 4d0bdba301..378ceb010f 100644 --- a/mobile/lib/providers/cleanup.provider.dart +++ b/mobile/lib/providers/cleanup.provider.dart @@ -1,9 +1,9 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/providers/app_settings.provider.dart'; +import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart'; +import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; -import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/services/cleanup.service.dart'; class CleanupState { @@ -54,27 +54,25 @@ final cleanupProvider = StateNotifierProvider((re return CleanupNotifier( ref.watch(cleanupServiceProvider), ref.watch(currentUserProvider)?.id, - ref.watch(appSettingsServiceProvider), + ref.watch(settingsProvider), ); }); class CleanupNotifier extends StateNotifier { final CleanupService _cleanupService; final String? _userId; - final AppSettingsService _appSettingsService; + final SettingsRepository _settingsRepository; - CleanupNotifier(this._cleanupService, this._userId, this._appSettingsService) : super(const CleanupState()) { + CleanupNotifier(this._cleanupService, this._userId, this._settingsRepository) : super(const CleanupState()) { _loadPersistedSettings(); } void _loadPersistedSettings() { - final keepFavorites = _appSettingsService.getSetting(AppSettingsEnum.cleanupKeepFavorites); - final keepMediaTypeIndex = _appSettingsService.getSetting(AppSettingsEnum.cleanupKeepMediaType); - final keepAlbumIdsString = _appSettingsService.getSetting(AppSettingsEnum.cleanupKeepAlbumIds); - final cutoffDaysAgo = _appSettingsService.getSetting(AppSettingsEnum.cleanupCutoffDaysAgo); - - final keepMediaType = AssetKeepType.values[keepMediaTypeIndex.clamp(0, AssetKeepType.values.length - 1)]; - final keepAlbumIds = keepAlbumIdsString.isEmpty ? {} : keepAlbumIdsString.split(',').toSet(); + final cleanup = _settingsRepository.appConfig.cleanup; + final keepFavorites = cleanup.keepFavorites; + final keepMediaType = cleanup.keepMediaType; + final keepAlbumIds = cleanup.keepAlbumIds.toSet(); + final cutoffDaysAgo = cleanup.cutoffDaysAgo; final selectedDate = cutoffDaysAgo >= 0 ? DateTime.now().subtract(Duration(days: cutoffDaysAgo)) : null; state = state.copyWith( @@ -89,18 +87,18 @@ class CleanupNotifier extends StateNotifier { state = state.copyWith(selectedDate: date, assetsToDelete: []); if (date != null) { final daysAgo = DateTime.now().difference(date).inDays; - _appSettingsService.setSetting(AppSettingsEnum.cleanupCutoffDaysAgo, daysAgo); + _settingsRepository.write(.cleanupCutoffDaysAgo, daysAgo); } } void setKeepMediaType(AssetKeepType keepMediaType) { state = state.copyWith(keepMediaType: keepMediaType, assetsToDelete: []); - _appSettingsService.setSetting(AppSettingsEnum.cleanupKeepMediaType, keepMediaType.index); + _settingsRepository.write(.cleanupKeepMediaType, keepMediaType); } void setKeepFavorites(bool keepFavorites) { state = state.copyWith(keepFavorites: keepFavorites, assetsToDelete: []); - _appSettingsService.setSetting(AppSettingsEnum.cleanupKeepFavorites, keepFavorites); + _settingsRepository.write(.cleanupKeepFavorites, keepFavorites); } void toggleKeepAlbum(String albumId) { @@ -120,7 +118,7 @@ class CleanupNotifier extends StateNotifier { } void _persistExcludedAlbumIds(Set albumIds) { - _appSettingsService.setSetting(AppSettingsEnum.cleanupKeepAlbumIds, albumIds.join(',')); + _settingsRepository.write(.cleanupKeepAlbumIds, albumIds.toList()); } void cleanupStaleAlbumIds(Set existingAlbumIds) { @@ -133,8 +131,10 @@ class CleanupNotifier extends StateNotifier { } void applyDefaultAlbumSelections(List<(String id, String name)> albums) { - final isInitialized = _appSettingsService.getSetting(AppSettingsEnum.cleanupDefaultsInitialized); - if (isInitialized) return; + final isInitialized = _settingsRepository.appConfig.cleanup.defaultsInitialized; + if (isInitialized) { + return; + } final toKeep = _cleanupService.getDefaultKeepAlbumIds(albums); @@ -144,7 +144,7 @@ class CleanupNotifier extends StateNotifier { _persistExcludedAlbumIds(keepAlbumIds); } - _appSettingsService.setSetting(AppSettingsEnum.cleanupDefaultsInitialized, true); + _settingsRepository.write(.cleanupDefaultsInitialized, true); } Future scanAssets() async { diff --git a/mobile/lib/providers/infrastructure/action.provider.dart b/mobile/lib/providers/infrastructure/action.provider.dart index d0d1d5d424..6fdd9fc5c9 100644 --- a/mobile/lib/providers/infrastructure/action.provider.dart +++ b/mobile/lib/providers/infrastructure/action.provider.dart @@ -5,14 +5,19 @@ import 'package:background_downloader/background_downloader.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/domain/models/album/album.model.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/asset_edit.model.dart'; import 'package:immich_mobile/domain/services/asset.service.dart'; +import 'package:immich_mobile/domain/services/remote_album.service.dart'; import 'package:immich_mobile/models/download/livephotos_medatada.model.dart'; import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'; import 'package:immich_mobile/providers/backup/asset_upload_progress.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/album.provider.dart'; import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; import 'package:immich_mobile/providers/infrastructure/asset_viewer/asset.provider.dart' show assetExifProvider; +import 'package:immich_mobile/providers/infrastructure/tag.provider.dart'; +import 'package:immich_mobile/providers/server_info.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; import 'package:immich_mobile/providers/websocket.provider.dart'; @@ -20,6 +25,7 @@ import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/services/action.service.dart'; import 'package:immich_mobile/services/download.service.dart'; import 'package:immich_mobile/services/foreground_upload.service.dart'; +import 'package:immich_mobile/utils/semver.dart'; import 'package:immich_mobile/widgets/asset_grid/delete_dialog.dart'; import 'package:logging/logging.dart'; import 'package:openapi/api.dart'; @@ -30,11 +36,12 @@ class ActionResult { final int count; final bool success; final String? error; + final List remoteAssetIds; - const ActionResult({required this.count, required this.success, this.error}); + const ActionResult({required this.count, required this.success, this.error, this.remoteAssetIds = const []}); @override - String toString() => 'ActionResult(count: $count, success: $success, error: $error)'; + String toString() => 'ActionResult(count: $count, success: $success, error: $error, remoteAssetIds: $remoteAssetIds)'; } class ActionNotifier extends Notifier { @@ -239,6 +246,26 @@ class ActionNotifier extends Notifier { } } + Future emptyTrash(String userId) async { + try { + final count = await _service.emptyTrash(userId); + return ActionResult(count: count, success: true); + } catch (error, stack) { + _logger.severe('Failed to empty trash', error, stack); + return ActionResult(count: 0, success: false, error: error.toString()); + } + } + + Future restoreAllTrash(String userId) async { + try { + final count = await _service.restoreAllTrash(userId); + return ActionResult(count: count, success: true); + } catch (error, stack) { + _logger.severe('Failed to restore all trash assets', error, stack); + return ActionResult(count: 0, success: false, error: error.toString()); + } + } + Future trashRemoteAndDeleteLocal(ActionSource source) async { final ids = _getOwnedRemoteIdsForSource(source); final localIds = _getLocalIdsForSource(source); @@ -333,6 +360,69 @@ class ActionNotifier extends Notifier { } } + Future tagAssets(ActionSource source, BuildContext context) async { + final ids = _getOwnedRemoteIdsForSource(source); + try { + final count = await _service.tagAssets(ids, context); + if (count == null) { + return null; + } + + ref.invalidate(tagProvider); + return ActionResult(count: count, success: true); + } catch (error, stack) { + _logger.severe('Failed to tag assets', error, stack); + ref.invalidate(tagProvider); + return ActionResult(count: ids.length, success: false, error: error.toString()); + } + } + + Future addToAlbum(ActionSource source, RemoteAlbum album) async { + final selected = _getAssets(source).toList(growable: false); + if (selected.isEmpty) { + return const ActionResult(count: 0, success: true); + } + + final candidates = RemoteAlbumService.categorizeCandidates(selected); + final remoteIds = candidates.remoteAssetIds; + final localAssets = candidates.localAssetsToUpload; + final albumNotifier = ref.read(remoteAlbumProvider.notifier); + + int addedRemote = 0; + if (remoteIds.isNotEmpty) { + try { + addedRemote = await albumNotifier.addAssets(album.id, remoteIds); + } catch (error, stack) { + _logger.severe('Failed to add assets to album ${album.id}', error, stack); + return ActionResult(count: 0, success: false, error: error.toString()); + } + } + + // Keep the selection available for retry if the remote add fails. Once the + // album mutation succeeds, clear timeline selection so upload overlays can render. + if (source == ActionSource.timeline) { + ref.read(multiSelectProvider.notifier).reset(); + } + + if (localAssets.isEmpty) { + return ActionResult(count: addedRemote, success: true); + } + + final uploadResult = await upload( + source, + assets: localAssets, + onAssetUploaded: (asset, remoteId) async { + await albumNotifier.linkUploadedAssetToAlbum(album.id, asset, remoteId); + }, + ); + + return ActionResult( + count: addedRemote + uploadResult.count, + success: uploadResult.success, + error: uploadResult.error, + ); + } + Future removeFromAlbum(ActionSource source, String albumId) async { final ids = _getRemoteIdsForSource(source); try { @@ -425,11 +515,17 @@ class ActionNotifier extends Notifier { ActionSource source, BuildContext context, { Completer? cancelCompleter, + void Function(double progress)? onAssetDownloadProgress, }) async { final ids = _getAssets(source).toList(growable: false); try { - await _service.shareAssets(ids, context, cancelCompleter: cancelCompleter); + await _service.shareAssets( + ids, + context, + cancelCompleter: cancelCompleter, + onAssetDownloadProgress: onAssetDownloadProgress, + ); return ActionResult(count: ids.length, success: true); } catch (error, stack) { _logger.severe('Failed to share assets', error, stack); @@ -449,12 +545,24 @@ class ActionNotifier extends Notifier { } } - Future upload(ActionSource source, {List? assets}) async { + Future upload( + ActionSource source, { + List? assets, + FutureOr Function(LocalAsset asset, String remoteId)? onAssetUploaded, + }) async { final assetsToUpload = assets ?? _getAssets(source).whereType().toList(); + final assetById = {for (final a in assetsToUpload) a.id: a}; + final uploadedAssetIds = {}; + final failedAssetIds = {}; + final postUploadTasks = >[]; + if (assetsToUpload.isEmpty) { + return const ActionResult(count: 0, success: false, error: 'No assets to upload'); + } final progressNotifier = ref.read(assetUploadProgressProvider.notifier); final cancelToken = Completer(); ref.read(manualUploadCancelTokenProvider.notifier).state = cancelToken; + final remoteAssetIds = []; // Initialize progress for all assets for (final asset in assetsToUpload) { @@ -471,17 +579,45 @@ class ActionNotifier extends Notifier { progressNotifier.setProgress(localAssetId, progress); }, onSuccess: (localAssetId, remoteAssetId) { + remoteAssetIds.add(remoteAssetId); progressNotifier.remove(localAssetId); + uploadedAssetIds.add(localAssetId); + final asset = assetById[localAssetId]; + final callback = onAssetUploaded; + if (asset != null && callback != null) { + postUploadTasks.add( + Future.sync(() => callback(asset, remoteAssetId)).catchError((Object error, StackTrace stack) { + failedAssetIds.add(localAssetId); + progressNotifier.setError(localAssetId); + _logger.warning('Post-upload callback failed for $localAssetId', error, stack); + }), + ); + } }, onError: (localAssetId, errorMessage) { + failedAssetIds.add(localAssetId); progressNotifier.setError(localAssetId); }, ), ); - return ActionResult(count: assetsToUpload.length, success: true); + + await Future.wait(postUploadTasks); + final successCount = uploadedAssetIds.difference(failedAssetIds).length; + final isSuccess = successCount == assetsToUpload.length && failedAssetIds.isEmpty; + + return ActionResult( + count: successCount, + success: isSuccess, + error: isSuccess ? null : 'Failed to upload ${assetsToUpload.length - successCount} assets', + ); } catch (error, stack) { _logger.severe('Failed manually upload assets', error, stack); - return ActionResult(count: assetsToUpload.length, success: false, error: error.toString()); + + return ActionResult( + count: uploadedAssetIds.difference(failedAssetIds).length, + success: false, + error: error.toString(), + ); } finally { ref.read(manualUploadCancelTokenProvider.notifier).state = null; Future.delayed(const Duration(seconds: 2), () { @@ -498,14 +634,22 @@ class ActionNotifier extends Notifier { return ActionResult(count: ids.length, success: false, error: 'Expected single asset for applying edits'); } - final completer = ref.read(websocketProvider.notifier).waitForEvent("AssetEditReadyV1", (dynamic data) { - final eventAsset = SyncAssetV1.fromJson(data["asset"]); - return eventAsset?.id == ids.first; - }, const Duration(seconds: 10)); + Future editReady; + if (ref.read(serverInfoProvider).serverVersion >= const SemVer(major: 3, minor: 0, patch: 0)) { + editReady = ref.read(websocketProvider.notifier).waitForEvent("AssetEditReadyV2", (dynamic data) { + final eventAsset = SyncAssetV2.fromJson(data["asset"]); + return eventAsset?.id == ids.first; + }, const Duration(seconds: 10)); + } else { + editReady = ref.read(websocketProvider.notifier).waitForEvent("AssetEditReadyV1", (dynamic data) { + final eventAsset = SyncAssetV1.fromJson(data["asset"]); + return eventAsset?.id == ids.first; + }, const Duration(seconds: 10)); + } try { await _service.applyEdits(ids.first, edits); - await completer; + await editReady; return const ActionResult(count: 1, success: true); } catch (error, stack) { _logger.severe('Failed to apply edits to assets', error, stack); @@ -518,7 +662,9 @@ extension on Iterable { Iterable toIds() => map((e) => e.id); Iterable ownedAssets(String? ownerId) { - if (ownerId == null) return const []; + if (ownerId == null) { + return const []; + } return whereType().where((a) => a.ownerId == ownerId); } } diff --git a/mobile/lib/providers/infrastructure/album.provider.dart b/mobile/lib/providers/infrastructure/album.provider.dart index 1ddabc1604..379e7b3101 100644 --- a/mobile/lib/providers/infrastructure/album.provider.dart +++ b/mobile/lib/providers/infrastructure/album.provider.dart @@ -9,6 +9,7 @@ import 'package:immich_mobile/infrastructure/repositories/remote_album.repositor import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; import 'package:immich_mobile/providers/infrastructure/remote_album.provider.dart'; import 'package:immich_mobile/repositories/drift_album_api_repository.dart'; +import 'package:immich_mobile/services/foreground_upload.service.dart'; final localAlbumRepository = Provider( (ref) => DriftLocalAlbumRepository(ref.watch(driftProvider)), @@ -33,7 +34,11 @@ final remoteAlbumRepository = Provider( ); final remoteAlbumServiceProvider = Provider( - (ref) => RemoteAlbumService(ref.watch(remoteAlbumRepository), ref.watch(driftAlbumApiRepositoryProvider)), + (ref) => RemoteAlbumService( + ref.watch(remoteAlbumRepository), + ref.watch(driftAlbumApiRepositoryProvider), + ref.watch(foregroundUploadServiceProvider), + ), dependencies: [remoteAlbumRepository], ); diff --git a/mobile/lib/providers/infrastructure/cancel.provider.dart b/mobile/lib/providers/infrastructure/cancel.provider.dart index 6851861e1a..9d4a6790f2 100644 --- a/mobile/lib/providers/infrastructure/cancel.provider.dart +++ b/mobile/lib/providers/infrastructure/cancel.provider.dart @@ -1,8 +1,9 @@ +import 'dart:async'; + import 'package:hooks_riverpod/hooks_riverpod.dart'; -/// Provider holding a boolean function that returns true when cancellation is requested. -/// A computation running in the isolate uses the function to implement cooperative cancellation. -final cancellationProvider = Provider( +/// Holds the isolate's cancellation signal. +final cancellationProvider = Provider>( // This will be overridden in the isolate's container. // Throwing ensures it's not used without an override. (ref) => throw UnimplementedError( diff --git a/mobile/lib/providers/infrastructure/platform.provider.dart b/mobile/lib/providers/infrastructure/platform.provider.dart index 01d0f61d1c..9f85235927 100644 --- a/mobile/lib/providers/infrastructure/platform.provider.dart +++ b/mobile/lib/providers/infrastructure/platform.provider.dart @@ -3,9 +3,10 @@ import 'package:immich_mobile/domain/services/background_worker.service.dart'; import 'package:immich_mobile/platform/background_worker_api.g.dart'; import 'package:immich_mobile/platform/background_worker_lock_api.g.dart'; import 'package:immich_mobile/platform/connectivity_api.g.dart'; -import 'package:immich_mobile/platform/native_sync_api.g.dart'; import 'package:immich_mobile/platform/local_image_api.g.dart'; +import 'package:immich_mobile/platform/native_sync_api.g.dart'; import 'package:immich_mobile/platform/network_api.g.dart'; +import 'package:immich_mobile/platform/permission_api.g.dart'; import 'package:immich_mobile/platform/remote_image_api.g.dart'; final backgroundWorkerFgServiceProvider = Provider((_) => BackgroundWorkerFgService(BackgroundWorkerFgHostApi())); @@ -16,6 +17,8 @@ final backgroundWorkerLockServiceProvider = Provider((_) => NativeSyncApi()); +final permissionApiProvider = Provider((_) => PermissionApi()); + final connectivityApiProvider = Provider((_) => ConnectivityApi()); final localImageApi = LocalImageApi(); diff --git a/mobile/lib/providers/infrastructure/remote_album.provider.dart b/mobile/lib/providers/infrastructure/remote_album.provider.dart index 949e6d747e..b7d9b7cfd9 100644 --- a/mobile/lib/providers/infrastructure/remote_album.provider.dart +++ b/mobile/lib/providers/infrastructure/remote_album.provider.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:collection/collection.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/album/album.model.dart'; @@ -6,8 +8,12 @@ import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/domain/services/remote_album.service.dart'; import 'package:immich_mobile/models/albums/album_search.model.dart'; import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart'; +import 'package:openapi/api.dart' show Optional; +import 'package:immich_mobile/providers/album/pending_album_uploads.provider.dart'; +import 'package:immich_mobile/providers/backup/asset_upload_progress.provider.dart'; import 'package:immich_mobile/providers/infrastructure/album.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; +import 'package:immich_mobile/services/foreground_upload.service.dart'; import 'package:logging/logging.dart'; class RemoteAlbumState { @@ -24,7 +30,9 @@ class RemoteAlbumState { @override bool operator ==(covariant RemoteAlbumState other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } final listEquals = const DeepCollectionEquality().equals; return listEquals(other.albums, albums); @@ -103,10 +111,50 @@ class RemoteAlbumNotifier extends Notifier { } } + /// Creates an album from a heterogeneous asset selection. Already-remote + /// assets seed the album immediately; local-only assets are uploaded in the + /// background and linked one-by-one as each upload completes. + Future createAlbumWithAssets({ + required String title, + String? description, + Iterable assets = const [], + }) async { + try { + final currentUser = ref.read(currentUserProvider); + if (currentUser == null) { + throw Exception('User not logged in'); + } + + final candidates = RemoteAlbumService.categorizeCandidates(assets); + final album = await _remoteAlbumService.createAlbum( + title: title, + owner: currentUser, + description: description, + assetIds: candidates.remoteAssetIds, + ); + + state = state.copyWith(albums: [...state.albums, album]); + + if (candidates.localAssetsToUpload.isNotEmpty) { + unawaited( + addAssetsToAlbum( + album.id, + candidates.localAssetsToUpload, + ).then((_) {}).catchError((Object _, StackTrace _) {}), + ); + } + + return album; + } catch (error, stack) { + _logger.severe('Failed to create album with assets', error, stack); + rethrow; + } + } + Future updateAlbum( String albumId, { String? name, - String? description, + Optional description = const Optional.absent(), String? thumbnailAssetId, bool? isActivityEnabled, AlbumAssetOrder? order, @@ -153,8 +201,97 @@ class RemoteAlbumNotifier extends Notifier { return _remoteAlbumService.getAssets(albumId); } - Future addAssets(String albumId, List assetIds) { - return _remoteAlbumService.addAssets(albumId: albumId, assetIds: assetIds); + Future addAssets(String albumId, List assetIds) async { + final added = await _remoteAlbumService.addAssets(albumId: albumId, assetIds: assetIds); + if (added > 0) { + await _refreshAlbumInState(albumId); + } + return added; + } + + /// Links a freshly-uploaded local asset to an album using its new remote ID, + /// upserting a placeholder remote asset row so the local DB join survives + /// until the next sync catches up. + Future linkUploadedAssetToAlbum(String albumId, LocalAsset source, String remoteId) async { + final currentUser = ref.read(currentUserProvider); + if (currentUser == null) { + throw Exception('User not logged in'); + } + + final added = await _remoteAlbumService.linkUploadedAssetToAlbum(albumId, remoteId, currentUser, source); + if (added > 0) { + await _refreshAlbumInState(albumId); + } + return added; + } + + /// Adds a heterogeneous asset selection to an album. Already-remote assets + /// are linked immediately; local-only assets are queued in + /// [pendingAlbumUploadsProvider] (so the album page can show them with + /// progress indicators), uploaded, and linked one-by-one as each finishes. + Future addAssetsToAlbum(String albumId, Iterable assets) async { + final currentUser = ref.read(currentUserProvider); + if (currentUser == null) { + throw Exception('User not logged in'); + } + + final candidates = RemoteAlbumService.categorizeCandidates(assets); + final pendingNotifier = ref.read(pendingAlbumUploadsProvider(albumId).notifier); + pendingNotifier.enqueue(candidates.localAssetsToUpload); + + Completer? cancelToken; + if (candidates.localAssetsToUpload.isNotEmpty) { + cancelToken = Completer(); + ref.read(manualUploadCancelTokenProvider.notifier).state = cancelToken; + } + + try { + final added = await _remoteAlbumService.addAssetsToAlbum( + albumId: albumId, + uploader: currentUser, + candidates: candidates, + cancelToken: cancelToken, + uploadCallbacks: UploadCallbacks( + onProgress: (localAssetId, _, bytes, totalBytes) { + final progress = totalBytes > 0 ? bytes / totalBytes : 0.0; + pendingNotifier.updateProgress(localAssetId, progress); + }, + onSuccess: (localAssetId, _) => pendingNotifier.remove(localAssetId), + onError: (localAssetId, _) => pendingNotifier.markFailed(localAssetId), + ), + ); + if (added > 0) { + await _refreshAlbumInState(albumId); + } + return added; + } catch (error, stack) { + if (candidates.localAssetsToUpload.isNotEmpty) { + pendingNotifier.markAllFailed(); + } + _logger.severe('Failed to add assets to album $albumId', error, stack); + rethrow; + } finally { + if (cancelToken != null) { + if (cancelToken.isCompleted) { + pendingNotifier.clear(); + } + if (ref.read(manualUploadCancelTokenProvider) == cancelToken) { + ref.read(manualUploadCancelTokenProvider.notifier).state = null; + } + } + } + } + + /// Re-reads a single album from the local DB and replaces it in [state] so + /// that views bound to the album list (counts, thumbnails) reflect the + /// latest junction-table changes without a full `refresh()`. + Future _refreshAlbumInState(String albumId) async { + final updated = await _remoteAlbumService.get(albumId); + if (updated == null) { + return; + } + + state = state.copyWith(albums: state.albums.map((album) => album.id == albumId ? updated : album).toList()); } Future addUsers(String albumId, List userIds) { diff --git a/mobile/lib/providers/infrastructure/settings.provider.dart b/mobile/lib/providers/infrastructure/settings.provider.dart new file mode 100644 index 0000000000..d2b9dce1d6 --- /dev/null +++ b/mobile/lib/providers/infrastructure/settings.provider.dart @@ -0,0 +1,12 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/config/app_config.dart'; +import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart'; + +final settingsProvider = Provider.autoDispose((_) => SettingsRepository.instance); + +final appConfigProvider = Provider.autoDispose((ref) { + final repo = ref.watch(settingsProvider); + final subscription = repo.watchConfig().listen((event) => ref.state = event); + ref.onDispose(subscription.cancel); + return repo.appConfig; +}); diff --git a/mobile/lib/providers/infrastructure/sync.provider.dart b/mobile/lib/providers/infrastructure/sync.provider.dart index 5b9f29225e..700b51f12d 100644 --- a/mobile/lib/providers/infrastructure/sync.provider.dart +++ b/mobile/lib/providers/infrastructure/sync.provider.dart @@ -11,8 +11,8 @@ import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; import 'package:immich_mobile/providers/infrastructure/cancel.provider.dart'; import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; import 'package:immich_mobile/providers/infrastructure/platform.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/storage.provider.dart'; -import 'package:immich_mobile/repositories/local_files_manager.repository.dart'; +import 'package:immich_mobile/repositories/asset_media.repository.dart'; +import 'package:immich_mobile/repositories/permission.repository.dart'; final syncMigrationRepositoryProvider = Provider((ref) => SyncMigrationRepository(ref.watch(driftProvider))); @@ -22,11 +22,11 @@ final syncStreamServiceProvider = Provider( syncStreamRepository: ref.watch(syncStreamRepositoryProvider), localAssetRepository: ref.watch(localAssetRepository), trashedLocalAssetRepository: ref.watch(trashedLocalAssetRepository), - localFilesManager: ref.watch(localFilesManagerRepositoryProvider), - storageRepository: ref.watch(storageRepositoryProvider), + assetMediaRepository: ref.watch(assetMediaRepositoryProvider), + permissionRepository: ref.watch(permissionRepositoryProvider), syncMigrationRepository: ref.watch(syncMigrationRepositoryProvider), api: ref.watch(apiServiceProvider), - cancelChecker: ref.watch(cancellationProvider), + cancellation: ref.watch(cancellationProvider), ), ); @@ -39,9 +39,10 @@ final localSyncServiceProvider = Provider( localAlbumRepository: ref.watch(localAlbumRepository), localAssetRepository: ref.watch(localAssetRepository), trashedLocalAssetRepository: ref.watch(trashedLocalAssetRepository), - localFilesManager: ref.watch(localFilesManagerRepositoryProvider), - storageRepository: ref.watch(storageRepositoryProvider), + assetMediaRepository: ref.watch(assetMediaRepositoryProvider), + permissionRepository: ref.watch(permissionRepositoryProvider), nativeSyncApi: ref.watch(nativeSyncApiProvider), + cancellation: ref.watch(cancellationProvider), ), ); @@ -51,5 +52,6 @@ final hashServiceProvider = Provider( localAssetRepository: ref.watch(localAssetRepository), nativeSyncApi: ref.watch(nativeSyncApiProvider), trashedLocalAssetRepository: ref.watch(trashedLocalAssetRepository), + cancellation: ref.watch(cancellationProvider), ), ); diff --git a/mobile/lib/providers/infrastructure/tag.provider.dart b/mobile/lib/providers/infrastructure/tag.provider.dart index 23d4d86861..2d527768c5 100644 --- a/mobile/lib/providers/infrastructure/tag.provider.dart +++ b/mobile/lib/providers/infrastructure/tag.provider.dart @@ -1,16 +1,22 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/tag.model.dart'; -import 'package:immich_mobile/infrastructure/repositories/tags_api.repository.dart'; +import 'package:immich_mobile/domain/services/tag.service.dart'; class TagNotifier extends AsyncNotifier> { @override Future> build() async { - final repo = ref.read(tagsApiRepositoryProvider); - final allTags = await repo.getAllTags(); - if (allTags == null) { - return {}; - } - return allTags.map((t) => Tag.fromDto(t)).toSet(); + return ref.watch(tagServiceProvider).getAllTags(); + } + + Future bulkTagAssets(List assetIds, List tagIds) async { + return ref.read(tagServiceProvider).bulkTagAssets(assetIds, tagIds); + } + + Future> upsertTags(List tags) async { + final upsertedTags = await ref.read(tagServiceProvider).upsertTags(tags); + + state = AsyncValue.data({...?state.valueOrNull, ...upsertedTags}); + return upsertedTags; } } diff --git a/mobile/lib/providers/infrastructure/timeline.provider.dart b/mobile/lib/providers/infrastructure/timeline.provider.dart index 06ec0242b2..b22c693033 100644 --- a/mobile/lib/providers/infrastructure/timeline.provider.dart +++ b/mobile/lib/providers/infrastructure/timeline.provider.dart @@ -3,7 +3,7 @@ import 'package:immich_mobile/domain/services/timeline.service.dart'; import 'package:immich_mobile/infrastructure/repositories/timeline.repository.dart'; import 'package:immich_mobile/presentation/widgets/timeline/timeline.state.dart'; import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/setting.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; final timelineRepositoryProvider = Provider( @@ -29,7 +29,7 @@ final timelineServiceProvider = Provider( final timelineFactoryProvider = Provider( (ref) => TimelineFactory( timelineRepository: ref.watch(timelineRepositoryProvider), - settingsService: ref.watch(settingsProvider), + settingsRepository: ref.watch(settingsProvider), ), ); diff --git a/mobile/lib/providers/infrastructure/user_metadata.provider.dart b/mobile/lib/providers/infrastructure/user_metadata.provider.dart index 9a463463f5..e5b9aa2a6e 100644 --- a/mobile/lib/providers/infrastructure/user_metadata.provider.dart +++ b/mobile/lib/providers/infrastructure/user_metadata.provider.dart @@ -11,7 +11,9 @@ final userMetadataRepository = Provider( final userMetadataProvider = FutureProvider>((ref) async { final repository = ref.watch(userMetadataRepository); final user = ref.watch(currentUserProvider); - if (user == null) return []; + if (user == null) { + return []; + } return repository.getUserMetadata(user.id); }); diff --git a/mobile/lib/providers/map/map_state.provider.dart b/mobile/lib/providers/map/map_state.provider.dart index 63b277ac83..b643264dca 100644 --- a/mobile/lib/providers/map/map_state.provider.dart +++ b/mobile/lib/providers/map/map_state.provider.dart @@ -1,38 +1,37 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/models/map/map_state.model.dart'; -import 'package:immich_mobile/providers/app_settings.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; -import 'package:immich_mobile/services/app_settings.service.dart'; final mapStateNotifierProvider = NotifierProvider(MapStateNotifier.new); class MapStateNotifier extends Notifier { @override MapState build() { - final appSettingsProvider = ref.read(appSettingsServiceProvider); + final mapConfig = ref.read(appConfigProvider.select((config) => config.map)); final lightStyleUrl = ref.read(serverInfoProvider).serverConfig.mapLightStyleUrl; final darkStyleUrl = ref.read(serverInfoProvider).serverConfig.mapDarkStyleUrl; return MapState( - themeMode: ThemeMode.values[appSettingsProvider.getSetting(AppSettingsEnum.mapThemeMode)], - showFavoriteOnly: appSettingsProvider.getSetting(AppSettingsEnum.mapShowFavoriteOnly), - includeArchived: appSettingsProvider.getSetting(AppSettingsEnum.mapIncludeArchived), - withPartners: appSettingsProvider.getSetting(AppSettingsEnum.mapwithPartners), - relativeTime: appSettingsProvider.getSetting(AppSettingsEnum.mapRelativeDate), + themeMode: mapConfig.themeMode, + showFavoriteOnly: mapConfig.favoritesOnly, + includeArchived: mapConfig.includeArchived, + withPartners: mapConfig.withPartners, + relativeTime: mapConfig.relativeDays, lightStyleFetched: AsyncData(lightStyleUrl), darkStyleFetched: AsyncData(darkStyleUrl), ); } void switchTheme(ThemeMode mode) { - ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.mapThemeMode, mode.index); + ref.read(settingsProvider).write(.mapThemeMode, mode); state = state.copyWith(themeMode: mode); } void switchFavoriteOnly(bool isFavoriteOnly) { - ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.mapShowFavoriteOnly, isFavoriteOnly); + ref.read(settingsProvider).write(.mapShowFavoriteOnly, isFavoriteOnly); state = state.copyWith(showFavoriteOnly: isFavoriteOnly, shouldRefetchMarkers: true); } @@ -41,17 +40,17 @@ class MapStateNotifier extends Notifier { } void switchIncludeArchived(bool isIncludeArchived) { - ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.mapIncludeArchived, isIncludeArchived); + ref.read(settingsProvider).write(.mapIncludeArchived, isIncludeArchived); state = state.copyWith(includeArchived: isIncludeArchived, shouldRefetchMarkers: true); } void switchWithPartners(bool isWithPartners) { - ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.mapwithPartners, isWithPartners); + ref.read(settingsProvider).write(.mapWithPartners, isWithPartners); state = state.copyWith(withPartners: isWithPartners, shouldRefetchMarkers: true); } void setRelativeTime(int relativeTime) { - ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.mapRelativeDate, relativeTime); + ref.read(settingsProvider).write(.mapRelativeDate, relativeTime); state = state.copyWith(relativeTime: relativeTime, shouldRefetchMarkers: true); } } diff --git a/mobile/lib/providers/search/search_filter.provider.dart b/mobile/lib/providers/search/search_filter.provider.dart index 3040ecd808..b171de50c7 100644 --- a/mobile/lib/providers/search/search_filter.provider.dart +++ b/mobile/lib/providers/search/search_filter.provider.dart @@ -13,7 +13,9 @@ class SearchSuggestionArgs { @override bool operator ==(Object other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } return other is SearchSuggestionArgs && other.type == type && diff --git a/mobile/lib/providers/search/search_page_state.provider.dart b/mobile/lib/providers/search/search_page_state.provider.dart index 23d5606922..c6a72c922e 100644 --- a/mobile/lib/providers/search/search_page_state.provider.dart +++ b/mobile/lib/providers/search/search_page_state.provider.dart @@ -29,7 +29,7 @@ final getAllPlacesProvider = FutureProvider.autoDispose SearchCuratedContent(label: data.exifInfo!.city!, id: data.id)) + .map((data) => SearchCuratedContent(label: data.exifInfo.orElse(null)!.city.orElse(null)!, id: data.id)) .toList(); return curatedContent; diff --git a/mobile/lib/providers/sync_status.provider.dart b/mobile/lib/providers/sync_status.provider.dart index 203184fc87..8d7266abf7 100644 --- a/mobile/lib/providers/sync_status.provider.dart +++ b/mobile/lib/providers/sync_status.provider.dart @@ -56,7 +56,9 @@ class SyncStatusState { @override bool operator ==(Object other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } return other is SyncStatusState && other.remoteSyncStatus == remoteSyncStatus && other.localSyncStatus == localSyncStatus && diff --git a/mobile/lib/providers/theme.provider.dart b/mobile/lib/providers/theme.provider.dart index 1d5511f1ff..962781586e 100644 --- a/mobile/lib/providers/theme.provider.dart +++ b/mobile/lib/providers/theme.provider.dart @@ -1,58 +1,17 @@ -import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; - -import 'package:immich_mobile/constants/colors.dart'; +import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; import 'package:immich_mobile/theme/color_scheme.dart'; -import 'package:immich_mobile/theme/theme_data.dart'; import 'package:immich_mobile/theme/dynamic_theme.dart'; -import 'package:immich_mobile/providers/app_settings.provider.dart'; -import 'package:immich_mobile/services/app_settings.service.dart'; -import 'package:immich_mobile/utils/debug_print.dart'; - -final immichThemeModeProvider = StateProvider((ref) { - final themeMode = ref.watch(appSettingsServiceProvider).getSetting(AppSettingsEnum.themeMode); - - dPrint(() => "Current themeMode $themeMode"); - - if (themeMode == ThemeMode.light.name) { - return ThemeMode.light; - } else if (themeMode == ThemeMode.dark.name) { - return ThemeMode.dark; - } else { - return ThemeMode.system; - } -}); - -final immichThemePresetProvider = StateProvider((ref) { - final appSettingsProvider = ref.watch(appSettingsServiceProvider); - final primaryColorPreset = appSettingsProvider.getSetting(AppSettingsEnum.primaryColor); - - dPrint(() => "Current theme preset $primaryColorPreset"); - - try { - return ImmichColorPreset.values.firstWhere((e) => e.name == primaryColorPreset); - } catch (e) { - dPrint(() => "Theme preset $primaryColorPreset not found. Applying default preset."); - appSettingsProvider.setSetting(AppSettingsEnum.primaryColor, defaultColorPresetName); - return defaultColorPreset; - } -}); - -final dynamicThemeSettingProvider = StateProvider((ref) { - return ref.watch(appSettingsServiceProvider).getSetting(AppSettingsEnum.dynamicTheme); -}); - -final colorfulInterfaceSettingProvider = StateProvider((ref) { - return ref.watch(appSettingsServiceProvider).getSetting(AppSettingsEnum.colorfulInterface); -}); +import 'package:immich_mobile/theme/theme_data.dart'; // Provider for current selected theme final immichThemeProvider = StateProvider((ref) { - final primaryColorPreset = ref.read(immichThemePresetProvider); - final useSystemColor = ref.watch(dynamicThemeSettingProvider); - final useColorfulInterface = ref.watch(colorfulInterfaceSettingProvider); - final ImmichTheme? dynamicTheme = DynamicTheme.theme; - final currentTheme = (useSystemColor && dynamicTheme != null) ? dynamicTheme : primaryColorPreset.themeOfPreset; + final themeConfig = ref.watch(appConfigProvider.select((config) => config.theme)); - return useColorfulInterface ? currentTheme : decolorizeSurfaces(theme: currentTheme); + final ImmichTheme? dynamicTheme = DynamicTheme.theme; + final currentTheme = (themeConfig.dynamicTheme && dynamicTheme != null) + ? dynamicTheme + : themeConfig.primaryColor.themeOfPreset; + + return themeConfig.colorfulInterface ? currentTheme : decolorizeSurfaces(theme: currentTheme); }); diff --git a/mobile/lib/providers/timeline/multiselect.provider.dart b/mobile/lib/providers/timeline/multiselect.provider.dart index 6e375f3852..10c8bb86b6 100644 --- a/mobile/lib/providers/timeline/multiselect.provider.dart +++ b/mobile/lib/providers/timeline/multiselect.provider.dart @@ -48,7 +48,9 @@ class MultiSelectState { @override bool operator ==(covariant MultiSelectState other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } final setEquals = const DeepCollectionEquality().equals; return setEquals(other.selectedAssets, selectedAssets) && @@ -124,7 +126,9 @@ class MultiSelectNotifier extends Notifier { } void toggleBucketSelectionByAssets(List bucketAssets) { - if (bucketAssets.isEmpty) return; + if (bucketAssets.isEmpty) { + return; + } // Check if all assets in this bucket are currently selected final allSelected = bucketAssets.every((asset) => state.selectedAssets.contains(asset)); @@ -150,7 +154,9 @@ class MultiSelectNotifier extends Notifier { final bucketSelectionProvider = Provider.family>((ref, bucketAssets) { final selectedAssets = ref.watch(multiSelectProvider.select((s) => s.selectedAssets)); - if (bucketAssets.isEmpty) return false; + if (bucketAssets.isEmpty) { + return false; + } // Check if all assets in the bucket are selected return bucketAssets.every((asset) => selectedAssets.contains(asset)); diff --git a/mobile/lib/providers/upload_profile_image.provider.dart b/mobile/lib/providers/upload_profile_image.provider.dart index a2b7a23f05..77772b0205 100644 --- a/mobile/lib/providers/upload_profile_image.provider.dart +++ b/mobile/lib/providers/upload_profile_image.provider.dart @@ -46,7 +46,9 @@ class UploadProfileImageState { @override bool operator ==(Object other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } return other is UploadProfileImageState && other.status == status && other.profileImagePath == profileImagePath; } diff --git a/mobile/lib/providers/view_intent/view_intent_file_path.provider.dart b/mobile/lib/providers/view_intent/view_intent_file_path.provider.dart new file mode 100644 index 0000000000..75cd304a3a --- /dev/null +++ b/mobile/lib/providers/view_intent/view_intent_file_path.provider.dart @@ -0,0 +1,31 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +class ViewIntentFilePathNotifier extends Notifier { + @override + String? build() => null; + + void setPath(String path) { + if (state == path) { + return; + } + state = path; + } + + void clear() { + if (state == null) { + return; + } + state = null; + } + + void clearIfMatch(String path) { + if (state != path) { + return; + } + state = null; + } +} + +final viewIntentFilePathProvider = NotifierProvider( + ViewIntentFilePathNotifier.new, +); diff --git a/mobile/lib/providers/view_intent/view_intent_handler.provider.dart b/mobile/lib/providers/view_intent/view_intent_handler.provider.dart new file mode 100644 index 0000000000..b266887cab --- /dev/null +++ b/mobile/lib/providers/view_intent/view_intent_handler.provider.dart @@ -0,0 +1,23 @@ +import 'dart:io'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/platform/view_intent_api.g.dart'; +import 'package:immich_mobile/providers/view_intent/view_intent_handler_android.dart'; +import 'package:immich_mobile/providers/view_intent/view_intent_handler_stub.dart'; + +abstract class ViewIntentHandler { + void init(); + + Future onAppResumed(); + + Future flushDeferredViewIntent(); + + Future handle(ViewIntentPayload attachment); +} + +final viewIntentHandlerProvider = Provider((ref) { + if (Platform.isAndroid) { + return AndroidViewIntentHandler(ref); + } + + return const StubViewIntentHandler(); +}); diff --git a/mobile/lib/providers/view_intent/view_intent_handler_android.dart b/mobile/lib/providers/view_intent/view_intent_handler_android.dart new file mode 100644 index 0000000000..c00ff38648 --- /dev/null +++ b/mobile/lib/providers/view_intent/view_intent_handler_android.dart @@ -0,0 +1,103 @@ +import 'dart:async'; + +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; +import 'package:immich_mobile/domain/services/timeline.service.dart'; +import 'package:immich_mobile/platform/view_intent_api.g.dart'; +import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'; +import 'package:immich_mobile/providers/auth.provider.dart'; +import 'package:immich_mobile/providers/view_intent/view_intent_file_path.provider.dart'; +import 'package:immich_mobile/providers/view_intent/view_intent_handler.provider.dart'; +import 'package:immich_mobile/providers/view_intent/view_intent_pending.provider.dart'; +import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/services/view_intent.service.dart'; +import 'package:immich_mobile/services/view_intent_asset_resolver.service.dart'; +import 'package:logging/logging.dart'; + +class AndroidViewIntentHandler implements ViewIntentHandler { + final Ref _ref; + final ViewIntentService _viewIntentService; + final ViewIntentAssetResolver _viewIntentAssetResolver; + final AppRouter _router; + static final Logger _logger = Logger('ViewIntentHandler'); + + AndroidViewIntentHandler(Ref ref) + : _ref = ref, + _viewIntentService = ref.read(viewIntentServiceProvider), + _viewIntentAssetResolver = ref.read(viewIntentAssetResolverProvider), + _router = ref.watch(appRouterProvider); + + @override + void init() { + // Covers cold start from a view intent before the first lifecycle "resumed". + unawaited(onAppResumed()); + } + + @override + Future onAppResumed() => _checkForViewIntent(); + + @override + Future flushDeferredViewIntent() => _flushPending(); + + Future _checkForViewIntent() async { + final attachment = await _viewIntentService.consumeViewIntent(); + if (attachment != null) { + await handle(attachment); + return; + } + + if (_ref.read(viewIntentPendingProvider) == null) { + await _viewIntentService.cleanupStaleTempFiles(); + } + } + + Future _flushPending() async { + final pendingAttachment = _ref.read(viewIntentPendingProvider.notifier).takeIfFresh(); + _logger.info('flushPending, pendingAttachment:$pendingAttachment'); + if (pendingAttachment != null) { + await handle(pendingAttachment); + } + } + + @override + Future handle(ViewIntentPayload attachment) async { + _logger.info( + 'handle attachment, mimeType:${attachment.mimeType}, localAssetId=${attachment.localAssetId}, path=${attachment.path}, isAuthenticated:${_ref.read(authProvider).isAuthenticated}', + ); + + if (!_ref.read(authProvider).isAuthenticated) { + _ref.read(viewIntentPendingProvider.notifier).defer(attachment); + return; + } + + final resolvedAsset = await _viewIntentAssetResolver.resolve(attachment); + _logger.fine('resolved view intent asset: ${resolvedAsset.asset}'); + await _openAssetViewer( + resolvedAsset.asset, + resolvedAsset.timelineService, + viewIntentFilePath: resolvedAsset.viewIntentFilePath, + ); + } + + Future _openAssetViewer(BaseAsset asset, TimelineService timelineService, {String? viewIntentFilePath}) async { + final notifier = _ref.read(assetViewerProvider.notifier); + notifier.reset(); + if (asset.isVideo) { + notifier.setControls(false); + } + notifier.setAsset(asset); + + if (viewIntentFilePath != null) { + _ref.read(viewIntentFilePathProvider.notifier).setPath(viewIntentFilePath); + unawaited(_viewIntentService.setManagedTempFilePath(viewIntentFilePath)); + } else { + _ref.read(viewIntentFilePathProvider.notifier).clear(); + unawaited(_viewIntentService.cleanupManagedTempFile()); + } + + await _router.replaceAll([ + const TabShellRoute(), + AssetViewerRoute(initialIndex: 0, timelineService: timelineService), + ]); + } +} diff --git a/mobile/lib/providers/view_intent/view_intent_handler_stub.dart b/mobile/lib/providers/view_intent/view_intent_handler_stub.dart new file mode 100644 index 0000000000..ebc6d7425b --- /dev/null +++ b/mobile/lib/providers/view_intent/view_intent_handler_stub.dart @@ -0,0 +1,18 @@ +import 'package:immich_mobile/platform/view_intent_api.g.dart'; +import 'package:immich_mobile/providers/view_intent/view_intent_handler.provider.dart'; + +class StubViewIntentHandler implements ViewIntentHandler { + const StubViewIntentHandler(); + + @override + void init() {} + + @override + Future onAppResumed() async {} + + @override + Future flushDeferredViewIntent() async {} + + @override + Future handle(ViewIntentPayload attachment) async {} +} diff --git a/mobile/lib/providers/view_intent/view_intent_pending.provider.dart b/mobile/lib/providers/view_intent/view_intent_pending.provider.dart new file mode 100644 index 0000000000..c3f68eff79 --- /dev/null +++ b/mobile/lib/providers/view_intent/view_intent_pending.provider.dart @@ -0,0 +1,39 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/platform/view_intent_api.g.dart'; + +final viewIntentNowProvider = Provider((ref) => DateTime.now); + +final viewIntentPendingProvider = NotifierProvider( + ViewIntentPendingNotifier.new, +); + +class ViewIntentPendingNotifier extends Notifier { + static const _ttl = Duration(minutes: 10); + + DateTime? _deferredAt; + + @override + ViewIntentPayload? build() => null; + + void defer(ViewIntentPayload attachment) { + _deferredAt = ref.read(viewIntentNowProvider)(); + state = attachment; + } + + ViewIntentPayload? takeIfFresh() { + final attachment = state; + final deferredAt = _deferredAt; + state = null; + _deferredAt = null; + + if (attachment == null) { + return null; + } + + if (deferredAt != null && ref.read(viewIntentNowProvider)().difference(deferredAt) > _ttl) { + return null; + } + + return attachment; + } +} diff --git a/mobile/lib/providers/websocket.provider.dart b/mobile/lib/providers/websocket.provider.dart index 60afcec2d2..8d9bd5bfe3 100644 --- a/mobile/lib/providers/websocket.provider.dart +++ b/mobile/lib/providers/websocket.provider.dart @@ -7,6 +7,7 @@ import 'package:immich_mobile/infrastructure/repositories/network.repository.dar import 'package:immich_mobile/models/server_info/server_version.model.dart'; import 'package:immich_mobile/providers/auth.provider.dart'; import 'package:immich_mobile/providers/background_sync.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; import 'package:immich_mobile/utils/debounce.dart'; import 'package:immich_mobile/utils/debug_print.dart'; @@ -29,7 +30,9 @@ class WebsocketState { @override bool operator ==(Object other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } return other is WebsocketState && other.socket == socket && other.isConnected == isConnected; } @@ -58,7 +61,9 @@ class WebsocketNotifier extends StateNotifier { /// Connects websocket to server unless already connected void connect() { - if (state.isConnected) return; + if (state.isConnected) { + return; + } final authenticationState = _ref.read(authProvider); if (authenticationState.isAuthenticated) { @@ -188,7 +193,7 @@ class WebsocketNotifier extends StateNotifier { return; } - final isSyncAlbumEnabled = Store.get(StoreKey.syncAlbums, false); + final isSyncAlbumEnabled = _ref.read(appConfigProvider).backup.syncAlbums; try { unawaited( _ref.read(backgroundSyncProvider).syncWebsocketBatchV1(_batchedAssetUploadReady.toList()).then((_) { @@ -209,7 +214,7 @@ class WebsocketNotifier extends StateNotifier { return; } - final isSyncAlbumEnabled = Store.get(StoreKey.syncAlbums, false); + final isSyncAlbumEnabled = _ref.read(appConfigProvider).backup.syncAlbums; try { unawaited( _ref.read(backgroundSyncProvider).syncWebsocketBatchV2(_batchedAssetUploadReady.toList()).then((_) { diff --git a/mobile/lib/repositories/activity_api.repository.dart b/mobile/lib/repositories/activity_api.repository.dart index e8f9abc8c8..9ae83b0e7b 100644 --- a/mobile/lib/repositories/activity_api.repository.dart +++ b/mobile/lib/repositories/activity_api.repository.dart @@ -23,8 +23,8 @@ class ActivityApiRepository extends ApiRepository { final dto = ActivityCreateDto( albumId: albumId, type: type == ActivityType.comment ? ReactionType.comment : ReactionType.like, - assetId: assetId, - comment: comment, + assetId: assetId == null ? const Optional.absent() : Optional.present(assetId), + comment: comment == null ? const Optional.absent() : Optional.present(comment), ); final response = await checkNull(_api.createActivity(dto)); return _toActivity(response); @@ -45,6 +45,6 @@ class ActivityApiRepository extends ApiRepository { type: dto.type == ReactionType.comment ? ActivityType.comment : ActivityType.like, user: UserConverter.fromSimpleUserDto(dto.user), assetId: dto.assetId, - comment: dto.comment, + comment: dto.comment.orElse(null), ); } diff --git a/mobile/lib/repositories/api.repository.dart b/mobile/lib/repositories/api.repository.dart index 646e2480e9..8a5c690b3b 100644 --- a/mobile/lib/repositories/api.repository.dart +++ b/mobile/lib/repositories/api.repository.dart @@ -3,7 +3,9 @@ import 'package:immich_mobile/constants/errors.dart'; abstract class ApiRepository { Future checkNull(Future future) async { final response = await future; - if (response == null) throw const NoResponseDtoError(); + if (response == null) { + throw const NoResponseDtoError(); + } return response; } } diff --git a/mobile/lib/repositories/asset_api.repository.dart b/mobile/lib/repositories/asset_api.repository.dart index 2943177d60..4a23ced504 100644 --- a/mobile/lib/repositories/asset_api.repository.dart +++ b/mobile/lib/repositories/asset_api.repository.dart @@ -24,27 +24,45 @@ class AssetApiRepository extends ApiRepository { AssetApiRepository(this._api, this._stacksApi, this._trashApi); Future delete(List ids, bool force) async { - return _api.deleteAssets(AssetBulkDeleteDto(ids: ids, force: force)); + return _api.deleteAssets(AssetBulkDeleteDto(ids: ids, force: Optional.present(force))); } Future restoreTrash(List ids) async { await _trashApi.restoreAssets(BulkIdsDto(ids: ids)); } + Future emptyTrash() async { + final response = await _trashApi.emptyTrash(); + return response?.count ?? 0; + } + + Future restoreAllTrash() async { + final response = await _trashApi.restoreTrash(); + return response?.count ?? 0; + } + Future updateVisibility(List ids, AssetVisibilityEnum visibility) async { - return _api.updateAssets(AssetBulkUpdateDto(ids: ids, visibility: _mapVisibility(visibility))); + return _api.updateAssets(AssetBulkUpdateDto(ids: ids, visibility: Optional.present(_mapVisibility(visibility)))); } Future updateFavorite(List ids, bool isFavorite) async { - return _api.updateAssets(AssetBulkUpdateDto(ids: ids, isFavorite: isFavorite)); + return _api.updateAssets(AssetBulkUpdateDto(ids: ids, isFavorite: Optional.present(isFavorite))); } Future updateLocation(List ids, LatLng location) async { - return _api.updateAssets(AssetBulkUpdateDto(ids: ids, latitude: location.latitude, longitude: location.longitude)); + return _api.updateAssets( + AssetBulkUpdateDto( + ids: ids, + latitude: Optional.present(location.latitude), + longitude: Optional.present(location.longitude), + ), + ); } Future updateDateTime(List ids, DateTime dateTime) async { - return _api.updateAssets(AssetBulkUpdateDto(ids: ids, dateTimeOriginal: dateTime.toIso8601String())); + return _api.updateAssets( + AssetBulkUpdateDto(ids: ids, dateTimeOriginal: Optional.present(dateTime.toIso8601String())), + ); } Future stack(List ids) async { @@ -72,15 +90,15 @@ class AssetApiRepository extends ApiRepository { final response = await checkNull(_api.getAssetInfo(assetId)); // we need to get the MIME of the thumbnail once that gets added to the API - return response.originalMimeType; + return response.originalMimeType.orElse(null); } Future updateDescription(String assetId, String description) { - return _api.updateAsset(assetId, UpdateAssetDto(description: description)); + return _api.updateAsset(assetId, UpdateAssetDto(description: Optional.present(description))); } Future updateRating(String assetId, int rating) { - return _api.updateAsset(assetId, UpdateAssetDto(rating: rating)); + return _api.updateAsset(assetId, UpdateAssetDto(rating: Optional.present(rating))); } Future editAsset(String assetId, List edits) { diff --git a/mobile/lib/repositories/asset_media.repository.dart b/mobile/lib/repositories/asset_media.repository.dart index a2d8bfe162..e86a372768 100644 --- a/mobile/lib/repositories/asset_media.repository.dart +++ b/mobile/lib/repositories/asset_media.repository.dart @@ -1,26 +1,29 @@ import 'dart:async'; import 'dart:io'; +import 'package:background_downloader/background_downloader.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:flutter/widgets.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/constants.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/platform_extensions.dart'; -import 'package:immich_mobile/extensions/response_extensions.dart'; -import 'package:immich_mobile/repositories/asset_api.repository.dart'; +import 'package:immich_mobile/platform/native_sync_api.g.dart'; +import 'package:immich_mobile/providers/infrastructure/platform.provider.dart'; +import 'package:immich_mobile/services/api.service.dart'; +import 'package:immich_mobile/utils/image_url_builder.dart'; import 'package:logging/logging.dart'; -import 'package:path_provider/path_provider.dart'; import 'package:photo_manager/photo_manager.dart'; import 'package:share_plus/share_plus.dart'; -final assetMediaRepositoryProvider = Provider((ref) => AssetMediaRepository(ref.watch(assetApiRepositoryProvider))); +final assetMediaRepositoryProvider = Provider((ref) => AssetMediaRepository(ref.watch(nativeSyncApiProvider))); class AssetMediaRepository { - final AssetApiRepository _assetApiRepository; + final NativeSyncApi _nativeSyncApi; static final Logger _log = Logger("AssetMediaRepository"); - const AssetMediaRepository(this._assetApiRepository); + const AssetMediaRepository(this._nativeSyncApi); Future _androidSupportsTrash() async { if (Platform.isAndroid) { @@ -45,6 +48,27 @@ class AssetMediaRepository { return PhotoManager.editor.deleteWithIds(ids); } + Future _restoreFromTrashById(String mediaId, int type) async { + try { + return await _nativeSyncApi.restoreFromTrashById(mediaId, type); + } catch (e, s) { + _log.warning('Error restore file from trash by Id', e, s); + return false; + } + } + + Future> restoreAssetsFromTrash(Iterable assets) async { + final restoredIds = []; + for (final asset in assets) { + _log.info("Restoring from trash, localId: ${asset.id}, checksum: ${asset.checksum}"); + final result = await _restoreFromTrashById(asset.id, asset.type.index); + if (result) { + restoredIds.add(asset.id); + } + } + return restoredIds; + } + Future get(String id) async { final entity = await AssetEntity.fromId(id); return entity; @@ -81,10 +105,29 @@ class AssetMediaRepository { ); } - // TODO: make this more efficient - Future shareAssets(List assets, BuildContext context, {Completer? cancelCompleter}) async { + Future shareAssets( + List assets, + BuildContext context, { + Completer? cancelCompleter, + void Function(double progress)? onAssetDownloadProgress, + }) async { final downloadedXFiles = []; final tempFiles = []; + final totalAssets = assets.length; + var processedAssets = 0; + + void updateProgress([double currentAssetProgress = 0.0]) { + if (totalAssets <= 0) { + onAssetDownloadProgress?.call(1.0); + return; + } + + final normalizedAssetProgress = currentAssetProgress.clamp(0.0, 1.0); + final overallProgress = ((processedAssets + normalizedAssetProgress) / totalAssets).clamp(0.0, 1.0); + onAssetDownloadProgress?.call(overallProgress); + } + + updateProgress(); for (var asset in assets) { if (cancelCompleter != null && cancelCompleter.isCompleted) { @@ -101,6 +144,8 @@ class AssetMediaRepository { if (localId != null && !asset.isEdited) { File? f = await AssetEntity(id: localId, width: 1, height: 1, typeInt: 0).originFile; downloadedXFiles.add(XFile(f!.path)); + processedAssets++; + updateProgress(); if (CurrentPlatform.isIOS) { tempFiles.add(f); } @@ -108,22 +153,50 @@ class AssetMediaRepository { final remoteId = (asset is RemoteAsset) ? asset.id : asset.remoteId; if (remoteId == null) { _log.warning("Asset has no remote ID for sharing: $asset"); + processedAssets++; + updateProgress(); continue; } - final tempDir = await getTemporaryDirectory(); - final name = asset.name; - final tempFile = await File('${tempDir.path}/$name').create(); - final res = await _assetApiRepository.downloadAsset(remoteId, edited: true); + final taskId = 'share-$remoteId-${DateTime.now().microsecondsSinceEpoch}'; + final sanitizedFilename = asset.name.replaceAll(RegExp(r'[\\/]'), '_'); + final task = DownloadTask( + taskId: taskId, + url: getOriginalUrlForRemoteId(remoteId, edited: asset.isEdited), + headers: ApiService.getRequestHeaders(), + filename: sanitizedFilename, + baseDirectory: BaseDirectory.temporary, + group: kShareDownloadGroup, + updates: Updates.statusAndProgress, + ); + final statusUpdate = await FileDownloader().download( + task, + onProgress: (value) { + if (cancelCompleter != null && cancelCompleter.isCompleted) { + unawaited(FileDownloader().cancelTaskWithId(taskId)); + return; + } + updateProgress(value); + }, + ); - if (res.statusCode != 200) { - _log.severe("Download for $name failed", res.toLoggerString()); - continue; + if (cancelCompleter != null && cancelCompleter.isCompleted) { + await _cleanupTempFiles(tempFiles); + return 0; } - await tempFile.writeAsBytes(res.bodyBytes); - downloadedXFiles.add(XFile(tempFile.path)); - tempFiles.add(tempFile); + if (statusUpdate.status == TaskStatus.complete) { + final filePath = await task.filePath(); + final file = File(filePath); + tempFiles.add(file); + downloadedXFiles.add(XFile(filePath)); + processedAssets++; + updateProgress(); + continue; + } + _log.severe("Download for ${asset.name} failed with status ${statusUpdate.status}", statusUpdate.exception); + processedAssets++; + updateProgress(); } } diff --git a/mobile/lib/repositories/auth.repository.dart b/mobile/lib/repositories/auth.repository.dart index c16b728ae5..dd9aed3a03 100644 --- a/mobile/lib/repositories/auth.repository.dart +++ b/mobile/lib/repositories/auth.repository.dart @@ -1,46 +1,40 @@ -import 'dart:convert'; - import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/store.model.dart'; -import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/sync_stream.repository.dart'; import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart'; import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; -final authRepositoryProvider = Provider((ref) => AuthRepository(ref.watch(driftProvider))); +final authRepositoryProvider = Provider( + (ref) => AuthRepository(ref.watch(driftProvider), ref.watch(settingsProvider)), +); class AuthRepository { final Drift _drift; + final SettingsRepository _settings; - const AuthRepository(this._drift); + const AuthRepository(this._drift, this._settings); Future clearLocalData() async { await SyncStreamRepository(_drift).reset(); } bool getEndpointSwitchingFeature() { - return Store.tryGet(StoreKey.autoEndpointSwitching) ?? false; + return _settings.appConfig.network.autoEndpointSwitching; } String? getPreferredWifiName() { - return Store.tryGet(StoreKey.preferredWifiName); + return _settings.appConfig.network.preferredWifiName; } String? getLocalEndpoint() { - return Store.tryGet(StoreKey.localEndpoint); + return _settings.appConfig.network.localEndpoint; } List getExternalEndpointList() { - final jsonString = Store.tryGet(StoreKey.externalEndpointList); - - if (jsonString == null) { - return []; - } - - final List jsonList = jsonDecode(jsonString); - final endpointList = jsonList.map((e) => AuxilaryEndpoint.fromJson(e)).toList(); - - return endpointList; + return _settings.appConfig.network.externalEndpointList + .map((url) => AuxilaryEndpoint(url: url, status: .valid)) + .toList(); } } diff --git a/mobile/lib/repositories/auth_api.repository.dart b/mobile/lib/repositories/auth_api.repository.dart index 4b0880ddcf..05dc7f103a 100644 --- a/mobile/lib/repositories/auth_api.repository.dart +++ b/mobile/lib/repositories/auth_api.repository.dart @@ -13,7 +13,7 @@ class AuthApiRepository extends ApiRepository { AuthApiRepository(this._apiService); Future changePassword(String newPassword) async { - await _apiService.usersApi.updateMyUser(UserUpdateMeDto(password: newPassword)); + await _apiService.usersApi.updateMyUser(UserUpdateMeDto(password: Optional.present(newPassword))); } Future login(String email, String password) async { @@ -25,7 +25,9 @@ class AuthApiRepository extends ApiRepository { } Future logout() async { - if (_apiService.apiClient.basePath.isEmpty) return; + if (_apiService.apiClient.basePath.isEmpty) { + return; + } await _apiService.authenticationApi.logout().timeout(const Duration(seconds: 7)); } @@ -44,7 +46,7 @@ class AuthApiRepository extends ApiRepository { Future unlockPinCode(String pinCode) async { try { - await _apiService.authenticationApi.unlockAuthSession(SessionUnlockDto(pinCode: pinCode)); + await _apiService.authenticationApi.unlockAuthSession(SessionUnlockDto(pinCode: Optional.present(pinCode))); return true; } catch (_) { return false; diff --git a/mobile/lib/repositories/drift_album_api_repository.dart b/mobile/lib/repositories/drift_album_api_repository.dart index a0c7a3732a..c43e1fd4a0 100644 --- a/mobile/lib/repositories/drift_album_api_repository.dart +++ b/mobile/lib/repositories/drift_album_api_repository.dart @@ -22,7 +22,13 @@ class DriftAlbumApiRepository extends ApiRepository { String? description, }) async { final responseDto = await checkNull( - _api.createAlbum(CreateAlbumDto(albumName: name, description: description, assetIds: assetIds.toList())), + _api.createAlbum( + CreateAlbumDto( + albumName: name, + description: description == null ? const Optional.absent() : Optional.present(description), + assetIds: Optional.present(assetIds.toList()), + ), + ), ); return responseDto.toRemoteAlbum(owner); @@ -41,8 +47,14 @@ class DriftAlbumApiRepository extends ApiRepository { return (removed: removed, failed: failed); } - Future<({List added, List failed})> addAssets(String albumId, Iterable assetIds) async { - final response = await checkNull(_api.addAssetsToAlbum(albumId, BulkIdsDto(ids: assetIds.toList()))); + Future<({List added, List failed})> addAssets( + String albumId, + Iterable assetIds, { + Future? abortTrigger, + }) async { + final response = await checkNull( + _api.addAssetsToAlbum(albumId, BulkIdsDto(ids: assetIds.toList()), abortTrigger: abortTrigger), + ); final List added = [], failed = []; for (final dto in response) { if (dto.success) { @@ -59,7 +71,7 @@ class DriftAlbumApiRepository extends ApiRepository { String albumId, UserDto owner, { String? name, - String? description, + Optional description = const Optional.absent(), String? thumbnailAssetId, bool? isActivityEnabled, AlbumAssetOrder? order, @@ -73,11 +85,13 @@ class DriftAlbumApiRepository extends ApiRepository { _api.updateAlbumInfo( albumId, UpdateAlbumDto( - albumName: name, + albumName: name == null ? const Optional.absent() : Optional.present(name), description: description, - albumThumbnailAssetId: thumbnailAssetId, - isActivityEnabled: isActivityEnabled, - order: apiOrder, + albumThumbnailAssetId: thumbnailAssetId == null + ? const Optional.absent() + : Optional.present(thumbnailAssetId), + isActivityEnabled: isActivityEnabled == null ? const Optional.absent() : Optional.present(isActivityEnabled), + order: apiOrder == null ? const Optional.absent() : Optional.present(apiOrder), ), ), ); @@ -99,7 +113,9 @@ class DriftAlbumApiRepository extends ApiRepository { } Future setActivityStatus(String albumId, bool isEnabled) async { - final response = await checkNull(_api.updateAlbumInfo(albumId, UpdateAlbumDto(isActivityEnabled: isEnabled))); + final response = await checkNull( + _api.updateAlbumInfo(albumId, UpdateAlbumDto(isActivityEnabled: Optional.present(isEnabled))), + ); return response.isActivityEnabled; } } @@ -116,7 +132,7 @@ extension on AlbumResponseDto { updatedAt: updatedAt, thumbnailAssetId: albumThumbnailAssetId, isActivityEnabled: isActivityEnabled, - order: order == AssetOrder.asc ? AlbumAssetOrder.asc : AlbumAssetOrder.desc, + order: order.orElse(null) == AssetOrder.asc ? AlbumAssetOrder.asc : AlbumAssetOrder.desc, assetCount: assetCount, isShared: albumUsers.length > 2, ); diff --git a/mobile/lib/repositories/local_files_manager.repository.dart b/mobile/lib/repositories/local_files_manager.repository.dart deleted file mode 100644 index 6a6200b2e1..0000000000 --- a/mobile/lib/repositories/local_files_manager.repository.dart +++ /dev/null @@ -1,51 +0,0 @@ -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/services/local_files_manager.service.dart'; -import 'package:logging/logging.dart'; - -final localFilesManagerRepositoryProvider = Provider( - (ref) => LocalFilesManagerRepository(ref.watch(localFileManagerServiceProvider)), -); - -class LocalFilesManagerRepository { - LocalFilesManagerRepository(this._service); - - final Logger _logger = Logger('LocalFilesManagerRepo'); - final LocalFilesManagerService _service; - - Future moveToTrash(List mediaUrls) async { - return await _service.moveToTrash(mediaUrls); - } - - Future restoreFromTrash(String fileName, int type) async { - return await _service.restoreFromTrash(fileName, type); - } - - Future requestManageMediaPermission() async { - return await _service.requestManageMediaPermission(); - } - - Future hasManageMediaPermission() async { - return await _service.hasManageMediaPermission(); - } - - Future manageMediaPermission() async { - return await _service.manageMediaPermission(); - } - - Future> restoreAssetsFromTrash(Iterable assets) async { - final restoredIds = []; - for (final asset in assets) { - _logger.info("Restoring from trash, localId: ${asset.id}, remoteId: ${asset.checksum}"); - try { - final result = await _service.restoreFromTrashById(asset.id, asset.type.index); - if (result) { - restoredIds.add(asset.id); - } - } catch (e) { - _logger.warning("Restoring failure: $e"); - } - } - return restoredIds; - } -} diff --git a/mobile/lib/repositories/partner_api.repository.dart b/mobile/lib/repositories/partner_api.repository.dart index 69b6740cbe..eaca839bfd 100644 --- a/mobile/lib/repositories/partner_api.repository.dart +++ b/mobile/lib/repositories/partner_api.repository.dart @@ -16,7 +16,7 @@ class PartnerApiRepository extends ApiRepository { Future> getAll(Direction direction) async { final response = await checkNull( - _api.getPartners(direction == Direction.sharedByMe ? PartnerDirection.by : PartnerDirection.with_), + _api.getPartners(direction == Direction.sharedByMe ? PartnerDirection.sharedBy : PartnerDirection.sharedWith), ); return response.map(UserConverter.fromPartnerDto).toList(); } diff --git a/mobile/lib/repositories/permission.repository.dart b/mobile/lib/repositories/permission.repository.dart index 74230a3652..e4b6d99f25 100644 --- a/mobile/lib/repositories/permission.repository.dart +++ b/mobile/lib/repositories/permission.repository.dart @@ -1,12 +1,16 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/platform/permission_api.g.dart'; +import 'package:immich_mobile/providers/infrastructure/platform.provider.dart'; import 'package:permission_handler/permission_handler.dart'; -final permissionRepositoryProvider = Provider((_) { - return const PermissionRepository(); +final permissionRepositoryProvider = Provider((ref) { + return PermissionRepository(ref.watch(permissionApiProvider)); }); class PermissionRepository implements IPermissionRepository { - const PermissionRepository(); + final PermissionApi _permissionApi; + + const PermissionRepository(this._permissionApi); @override Future hasLocationWhenInUsePermission() { @@ -34,6 +38,21 @@ class PermissionRepository implements IPermissionRepository { Future openSettings() { return openAppSettings(); } + + @override + Future hasManageMediaPermission() { + return _permissionApi.hasManageMediaPermission(); + } + + @override + Future requestManageMediaPermission() { + return _permissionApi.requestManageMediaPermission(); + } + + @override + Future manageMediaPermission() { + return _permissionApi.manageMediaPermission(); + } } abstract interface class IPermissionRepository { @@ -42,4 +61,7 @@ abstract interface class IPermissionRepository { Future hasLocationAlwaysPermission(); Future requestLocationAlwaysPermission(); Future openSettings(); + Future hasManageMediaPermission(); + Future requestManageMediaPermission(); + Future manageMediaPermission(); } diff --git a/mobile/lib/repositories/person_api.repository.dart b/mobile/lib/repositories/person_api.repository.dart index bbf55e674a..26662601b7 100644 --- a/mobile/lib/repositories/person_api.repository.dart +++ b/mobile/lib/repositories/person_api.repository.dart @@ -18,7 +18,10 @@ class PersonApiRepository extends ApiRepository { Future update(String id, {String? name, DateTime? birthday}) async { final birthdayUtc = birthday == null ? null : DateTime.utc(birthday.year, birthday.month, birthday.day); - final dto = PersonUpdateDto(name: name, birthDate: birthdayUtc); + final dto = PersonUpdateDto( + name: name == null ? const Optional.absent() : Optional.present(name), + birthDate: birthdayUtc == null ? const Optional.absent() : Optional.present(birthdayUtc), + ); final response = await checkNull(_api.updatePerson(id, dto)); return _toPerson(response); } diff --git a/mobile/lib/repositories/sessions_api.repository.dart b/mobile/lib/repositories/sessions_api.repository.dart index f25e724f19..b2ed54c26c 100644 --- a/mobile/lib/repositories/sessions_api.repository.dart +++ b/mobile/lib/repositories/sessions_api.repository.dart @@ -15,7 +15,13 @@ class SessionsAPIRepository extends ApiRepository { Future createSession(String deviceType, String deviceOS, {int? duration}) async { final dto = await checkNull( - _api.createSession(SessionCreateDto(deviceType: deviceType, deviceOS: deviceOS, duration: duration)), + _api.createSession( + SessionCreateDto( + deviceType: Optional.present(deviceType), + deviceOS: Optional.present(deviceOS), + duration: duration == null ? const Optional.absent() : Optional.present(duration), + ), + ), ); return SessionCreateResponse( @@ -23,7 +29,7 @@ class SessionsAPIRepository extends ApiRepository { current: dto.current, deviceType: deviceType, deviceOS: deviceOS, - expiresAt: dto.expiresAt, + expiresAt: dto.expiresAt.orElse(null), createdAt: dto.createdAt, updatedAt: dto.updatedAt, token: dto.token, diff --git a/mobile/lib/repositories/upload.repository.dart b/mobile/lib/repositories/upload.repository.dart index 98c6202e19..68522490d8 100644 --- a/mobile/lib/repositories/upload.repository.dart +++ b/mobile/lib/repositories/upload.repository.dart @@ -161,7 +161,9 @@ class ProgressMultipartRequest extends MultipartRequest with Abortable { @override ByteStream finalize() { final byteStream = super.finalize(); - if (onProgress == null) return byteStream; + if (onProgress == null) { + return byteStream; + } final total = contentLength; var bytes = 0; diff --git a/mobile/lib/repositories/widget.repository.dart b/mobile/lib/repositories/widget.repository.dart index f21d31e1ec..87209ac983 100644 --- a/mobile/lib/repositories/widget.repository.dart +++ b/mobile/lib/repositories/widget.repository.dart @@ -1,5 +1,6 @@ import 'package:home_widget/home_widget.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/providers/infrastructure/platform.provider.dart'; final widgetRepositoryProvider = Provider((_) => const WidgetRepository()); @@ -14,7 +15,7 @@ class WidgetRepository { await HomeWidget.updateWidget(iOSName: iosName, qualifiedAndroidName: androidName); } - Future setAppGroupId(String appGroupId) async { - await HomeWidget.setAppGroupId(appGroupId); + Future setAppGroupId() async { + await HomeWidget.setAppGroupId(await networkApi.getAppGroupId()); } } diff --git a/mobile/lib/routing/auth_guard.dart b/mobile/lib/routing/auth_guard.dart index eaa821c0eb..2fc27be4f4 100644 --- a/mobile/lib/routing/auth_guard.dart +++ b/mobile/lib/routing/auth_guard.dart @@ -15,37 +15,62 @@ class AuthGuard extends AutoRouteGuard { final ApiService _apiService; final AuthService _authService; final _log = Logger("AuthGuard"); + bool _validateInFlight = false; AuthGuard(this._apiService, this._authService); @override - void onNavigation(NavigationResolver resolver, StackRouter router) async { - resolver.next(true); - + void onNavigation(NavigationResolver resolver, StackRouter router) { + // Synchronously check for the access token. auto_route awaits async + // guards, so we keep this function fully sync and validate the token in + // the background โ€” otherwise a slow validateAccessToken() request would + // block the route transition for as long as the OS-level HTTP timeout. try { - // Look in the store for an access token Store.get(StoreKey.accessToken); - - // Validate the access token with the server - final res = await _apiService.authenticationApi.validateAccessToken(); - if (res == null || res.authStatus != true) { - // If the access token is invalid, take user back to login - _log.fine('User token is invalid. Redirecting to login'); - unawaited(router.replaceAll([const LoginRoute()]).then((_) => _authService.clearLocalData())); - } } on StoreKeyNotFoundException catch (_) { - // If there is no access token, take us to the login page _log.warning('No access token in the store.'); + resolver.next(false); unawaited(router.replaceAll([const LoginRoute()])); return; + } + + resolver.next(true); + unawaited(_validateAccessTokenInBackground(router)); + } + + Future _validateAccessTokenInBackground(StackRouter router) async { + if (_validateInFlight) { + return; + } + final token = Store.tryGet(StoreKey.accessToken); + if (token == null) { + return; + } + _validateInFlight = true; + try { + final res = await _apiService.authenticationApi.validateAccessToken(); + if (res == null || res.authStatus != true) { + // Token may have changed during validation (user logged out + logged in + // again); only act if it still applies to the current session. + if (Store.tryGet(StoreKey.accessToken) != token) { + return; + } + _log.fine('User token is invalid. Redirecting to login'); + await router.replaceAll([const LoginRoute()]); + await _authService.clearLocalData(); + } } on ApiException catch (e) { - // On an unauthorized request, take us to the login page - if (e.code == HttpStatus.unauthorized) { - _log.warning("Unauthorized access token."); - unawaited(router.replaceAll([const LoginRoute()]).then((_) => _authService.clearLocalData())); + if (e.code != HttpStatus.unauthorized) { return; } + if (Store.tryGet(StoreKey.accessToken) != token) { + return; + } + _log.warning("Unauthorized access token."); + await router.replaceAll([const LoginRoute()]); + await _authService.clearLocalData(); } catch (e) { - // Otherwise, this is not fatal, but we still log the warning _log.warning('Error validating access token from server: $e'); + } finally { + _validateInFlight = false; } } } diff --git a/mobile/lib/routing/locked_guard.dart b/mobile/lib/routing/locked_guard.dart index ddb6a7e694..38484538e0 100644 --- a/mobile/lib/routing/locked_guard.dart +++ b/mobile/lib/routing/locked_guard.dart @@ -55,7 +55,7 @@ class LockedGuard extends AutoRouteGuard { return; } - await _apiService.authenticationApi.unlockAuthSession(SessionUnlockDto(pinCode: securePinCode)); + await _apiService.authenticationApi.unlockAuthSession(SessionUnlockDto(pinCode: Optional.present(securePinCode))); resolver.next(true); } on PlatformException catch (error) { diff --git a/mobile/lib/routing/router.dart b/mobile/lib/routing/router.dart index 0a9f6c4199..b39a568e26 100644 --- a/mobile/lib/routing/router.dart +++ b/mobile/lib/routing/router.dart @@ -58,7 +58,9 @@ import 'package:immich_mobile/presentation/pages/drift_person.page.dart'; import 'package:immich_mobile/presentation/pages/drift_place.page.dart'; import 'package:immich_mobile/presentation/pages/drift_place_detail.page.dart'; import 'package:immich_mobile/presentation/pages/drift_recently_taken.page.dart'; +import 'package:immich_mobile/presentation/pages/drift_recently_added.page.dart'; import 'package:immich_mobile/presentation/pages/drift_remote_album.page.dart'; +import 'package:immich_mobile/presentation/pages/drift_slideshow.page.dart'; import 'package:immich_mobile/presentation/pages/drift_trash.page.dart'; import 'package:immich_mobile/presentation/pages/drift_user_selection.page.dart'; import 'package:immich_mobile/presentation/pages/drift_video.page.dart'; @@ -168,6 +170,7 @@ class AppRouter extends RootStackRouter { AutoRoute(page: DriftAssetSelectionTimelineRoute.page, guards: [_authGuard, _duplicateGuard]), AutoRoute(page: DriftPartnerDetailRoute.page, guards: [_authGuard, _duplicateGuard]), AutoRoute(page: DriftRecentlyTakenRoute.page, guards: [_authGuard, _duplicateGuard]), + AutoRoute(page: DriftRecentlyAddedRoute.page, guards: [_authGuard, _duplicateGuard]), AutoRoute(page: DriftLocalAlbumsRoute.page, guards: [_authGuard, _duplicateGuard]), AutoRoute(page: DriftCreateAlbumRoute.page, guards: [_authGuard, _duplicateGuard]), AutoRoute(page: DriftPlaceRoute.page, guards: [_authGuard, _duplicateGuard]), @@ -187,6 +190,7 @@ class AppRouter extends RootStackRouter { AutoRoute(page: AssetTroubleshootRoute.page, guards: [_authGuard, _duplicateGuard]), AutoRoute(page: DownloadInfoRoute.page, guards: [_authGuard, _duplicateGuard]), AutoRoute(page: CleanupPreviewRoute.page, guards: [_authGuard, _duplicateGuard]), + AutoRoute(page: DriftSlideshowRoute.page, guards: [_authGuard, _duplicateGuard]), // required to handle all deeplinks in deep_link.service.dart // auto_route_library#1722 RedirectRoute(path: '*', redirectTo: '/'), diff --git a/mobile/lib/routing/router.gr.dart b/mobile/lib/routing/router.gr.dart index c025da0f73..a4b538d789 100644 --- a/mobile/lib/routing/router.gr.dart +++ b/mobile/lib/routing/router.gr.dart @@ -1047,6 +1047,22 @@ class DriftPlaceRouteArgs { int get hashCode => key.hashCode ^ currentLocation.hashCode; } +/// generated route for +/// [DriftRecentlyAddedPage] +class DriftRecentlyAddedRoute extends PageRouteInfo { + const DriftRecentlyAddedRoute({List? children}) + : super(DriftRecentlyAddedRoute.name, initialChildren: children); + + static const String name = 'DriftRecentlyAddedRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const DriftRecentlyAddedPage(); + }, + ); +} + /// generated route for /// [DriftRecentlyTakenPage] class DriftRecentlyTakenRoute extends PageRouteInfo { @@ -1079,6 +1095,53 @@ class DriftSearchRoute extends PageRouteInfo { ); } +/// generated route for +/// [DriftSlideshowPage] +class DriftSlideshowRoute extends PageRouteInfo { + DriftSlideshowRoute({ + Key? key, + required TimelineService timeline, + List? children, + }) : super( + DriftSlideshowRoute.name, + args: DriftSlideshowRouteArgs(key: key, timeline: timeline), + initialChildren: children, + ); + + static const String name = 'DriftSlideshowRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + final args = data.argsAs(); + return DriftSlideshowPage(key: args.key, timeline: args.timeline); + }, + ); +} + +class DriftSlideshowRouteArgs { + const DriftSlideshowRouteArgs({this.key, required this.timeline}); + + final Key? key; + + final TimelineService timeline; + + @override + String toString() { + return 'DriftSlideshowRouteArgs{key: $key, timeline: $timeline}'; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (other is! DriftSlideshowRouteArgs) return false; + return key == other.key && timeline == other.timeline; + } + + @override + int get hashCode => key.hashCode ^ timeline.hashCode; +} + /// generated route for /// [DriftTrashPage] class DriftTrashRoute extends PageRouteInfo { diff --git a/mobile/lib/services/action.service.dart b/mobile/lib/services/action.service.dart index 44b070e954..b22c6680a4 100644 --- a/mobile/lib/services/action.service.dart +++ b/mobile/lib/services/action.service.dart @@ -7,6 +7,7 @@ import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/asset_edit.model.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/domain/services/tag.service.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/platform_extensions.dart'; import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart'; @@ -23,6 +24,7 @@ import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/utils/timezone.dart'; import 'package:immich_mobile/widgets/common/date_time_picker.dart'; import 'package:immich_mobile/widgets/common/location_picker.dart'; +import 'package:immich_mobile/widgets/common/tag_picker.dart'; import 'package:maplibre_gl/maplibre_gl.dart' as maplibre; final actionServiceProvider = Provider( @@ -35,6 +37,7 @@ final actionServiceProvider = Provider( ref.watch(trashedLocalAssetRepository), ref.watch(assetMediaRepositoryProvider), ref.watch(downloadRepositoryProvider), + ref.watch(tagServiceProvider), ), ); @@ -47,6 +50,7 @@ class ActionService { final DriftTrashedLocalAssetRepository _trashedLocalAssetRepository; final AssetMediaRepository _assetMediaRepository; final DownloadRepository _downloadRepository; + final TagService _tagService; const ActionService( this._assetApiRepository, @@ -57,6 +61,7 @@ class ActionService { this._trashedLocalAssetRepository, this._assetMediaRepository, this._downloadRepository, + this._tagService, ); Future shareLink(List remoteIds, BuildContext context) async { @@ -108,6 +113,18 @@ class ActionService { await _remoteAssetRepository.restoreTrash(ids); } + Future emptyTrash(String userId) async { + final count = await _assetApiRepository.emptyTrash(); + await _remoteAssetRepository.emptyTrash(userId); + return count; + } + + Future restoreAllTrash(String userId) async { + final count = await _assetApiRepository.restoreAllTrash(); + await _remoteAssetRepository.restoreAllTrash(userId); + return count; + } + Future trashRemoteAndDeleteLocal(List remoteIds, List localIds) async { await _assetApiRepository.delete(remoteIds, false); await _remoteAssetRepository.trash(remoteIds); @@ -222,6 +239,26 @@ class ActionService { return true; } + Future tagAssets(List remoteIds, BuildContext context) async { + final tagResults = await showTagPickerModal(context: context); + if (tagResults == null) { + // user cancelled + return null; + } + + final selectedTagIds = Set.from(tagResults.$1); + final selectedNewTagValues = tagResults.$2; + + if (selectedNewTagValues.isNotEmpty) { + final upsertedTags = await _tagService.upsertTags(selectedNewTagValues.toList()); + selectedTagIds.addAll(upsertedTags.map((t) => t.id)); + } + if (selectedTagIds.isEmpty) { + return 0; + } + return _tagService.bulkTagAssets(remoteIds, selectedTagIds.toList()); + } + Future stack(String userId, List remoteIds) async { final stack = await _assetApiRepository.stack(remoteIds); await _remoteAssetRepository.stack(userId, stack); @@ -232,8 +269,18 @@ class ActionService { await _assetApiRepository.unStack(stackIds); } - Future shareAssets(List assets, BuildContext context, {Completer? cancelCompleter}) { - return _assetMediaRepository.shareAssets(assets, context, cancelCompleter: cancelCompleter); + Future shareAssets( + List assets, + BuildContext context, { + Completer? cancelCompleter, + void Function(double progress)? onAssetDownloadProgress, + }) { + return _assetMediaRepository.shareAssets( + assets, + context, + cancelCompleter: cancelCompleter, + onAssetDownloadProgress: onAssetDownloadProgress, + ); } Future> downloadAll(List assets) { diff --git a/mobile/lib/services/api.service.dart b/mobile/lib/services/api.service.dart index ec4720f313..a0828927ce 100644 --- a/mobile/lib/services/api.service.dart +++ b/mobile/lib/services/api.service.dart @@ -5,15 +5,15 @@ import 'dart:io'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/entities/store.entity.dart'; +import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/network.repository.dart'; -import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart'; import 'package:immich_mobile/utils/debug_print.dart'; import 'package:immich_mobile/utils/url_helper.dart'; import 'package:logging/logging.dart'; import 'package:openapi/api.dart'; class ApiService { - late ApiClient _apiClient; + final ApiClient _apiClient = ApiClient(basePath: ''); late UsersApi usersApi; late AuthenticationApi authenticationApi; @@ -54,7 +54,7 @@ class ApiService { } setEndpoint(String endpoint) { - _apiClient = ApiClient(basePath: endpoint); + _apiClient.basePath = endpoint; _apiClient.client = NetworkRepository.client; usersApi = UsersApi(_apiClient); authenticationApi = AuthenticationApi(_apiClient); @@ -177,28 +177,21 @@ class ApiService { if (serverEndpoint != null && serverEndpoint.isNotEmpty) { urls.add(serverEndpoint); } - final localEndpoint = Store.tryGet(StoreKey.localEndpoint); - if (localEndpoint != null && localEndpoint.isNotEmpty) { + final network = SettingsRepository.instance.appConfig.network; + final localEndpoint = network.localEndpoint; + if (localEndpoint.isNotEmpty) { urls.add(localEndpoint); } - final externalJson = Store.tryGet(StoreKey.externalEndpointList); - if (externalJson != null) { - final List list = jsonDecode(externalJson); - for (final entry in list) { - final url = AuxilaryEndpoint.fromJson(entry).url; - if (url.isNotEmpty) urls.add(url); + for (final url in network.externalEndpointList) { + if (url.isNotEmpty) { + urls.add(url); } } return urls; } static Map getRequestHeaders() { - var customHeadersStr = Store.get(StoreKey.customHeaders, ""); - if (customHeadersStr.isEmpty) { - return const {}; - } - - return (jsonDecode(customHeadersStr) as Map).cast(); + return SettingsRepository.instance.appConfig.network.customHeaders; } ApiClient get apiClient => _apiClient; diff --git a/mobile/lib/services/app_settings.service.dart b/mobile/lib/services/app_settings.service.dart index db4fc9965a..28bce32bb5 100644 --- a/mobile/lib/services/app_settings.service.dart +++ b/mobile/lib/services/app_settings.service.dart @@ -1,66 +1,11 @@ -import 'package:immich_mobile/constants/colors.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/entities/store.entity.dart'; enum AppSettingsEnum { - loadPreview(StoreKey.loadPreview, "loadPreview", true), - loadOriginal(StoreKey.loadOriginal, "loadOriginal", false), - themeMode(StoreKey.themeMode, "themeMode", "system"), // "light","dark","system" - primaryColor(StoreKey.primaryColor, "primaryColor", defaultColorPresetName), - dynamicTheme(StoreKey.dynamicTheme, "dynamicTheme", false), - colorfulInterface(StoreKey.colorfulInterface, "colorfulInterface", true), - tilesPerRow(StoreKey.tilesPerRow, "tilesPerRow", 4), - dynamicLayout(StoreKey.dynamicLayout, "dynamicLayout", false), - groupAssetsBy(StoreKey.groupAssetsBy, "groupBy", 0), - uploadErrorNotificationGracePeriod( - StoreKey.uploadErrorNotificationGracePeriod, - "uploadErrorNotificationGracePeriod", - 2, - ), - backgroundBackupTotalProgress(StoreKey.backgroundBackupTotalProgress, "backgroundBackupTotalProgress", true), - backgroundBackupSingleProgress( - StoreKey.backgroundBackupSingleProgress, - "backgroundBackupSingleProgress", - false, - ), - storageIndicator(StoreKey.storageIndicator, "storageIndicator", true), - thumbnailCacheSize(StoreKey.thumbnailCacheSize, "thumbnailCacheSize", 10000), - imageCacheSize(StoreKey.imageCacheSize, "imageCacheSize", 350), - albumThumbnailCacheSize(StoreKey.albumThumbnailCacheSize, "albumThumbnailCacheSize", 200), - selectedAlbumSortOrder(StoreKey.selectedAlbumSortOrder, "selectedAlbumSortOrder", 2), advancedTroubleshooting(StoreKey.advancedTroubleshooting, null, false), manageLocalMediaAndroid(StoreKey.manageLocalMediaAndroid, null, false), - logLevel(StoreKey.logLevel, null, 5), // Level.INFO = 5 - preferRemoteImage(StoreKey.preferRemoteImage, null, false), - loopVideo(StoreKey.loopVideo, "loopVideo", true), - loadOriginalVideo(StoreKey.loadOriginalVideo, "loadOriginalVideo", false), - autoPlayVideo(StoreKey.autoPlayVideo, "autoPlayVideo", true), - tapToNavigate(StoreKey.tapToNavigate, "tapToNavigate", false), - mapThemeMode(StoreKey.mapThemeMode, null, 0), - mapShowFavoriteOnly(StoreKey.mapShowFavoriteOnly, null, false), - mapIncludeArchived(StoreKey.mapIncludeArchived, null, false), - mapwithPartners(StoreKey.mapwithPartners, null, false), - mapRelativeDate(StoreKey.mapRelativeDate, null, 0), - allowSelfSignedSSLCert(StoreKey.selfSignedCert, null, false), - ignoreIcloudAssets(StoreKey.ignoreIcloudAssets, null, false), - selectedAlbumSortReverse(StoreKey.selectedAlbumSortReverse, null, true), enableHapticFeedback(StoreKey.enableHapticFeedback, null, true), - syncAlbums(StoreKey.syncAlbums, null, false), - autoEndpointSwitching(StoreKey.autoEndpointSwitching, null, false), - photoManagerCustomFilter(StoreKey.photoManagerCustomFilter, null, true), - betaTimeline(StoreKey.betaTimeline, null, true), - enableBackup(StoreKey.enableBackup, null, false), - useCellularForUploadVideos(StoreKey.useWifiForUploadVideos, null, false), - useCellularForUploadPhotos(StoreKey.useWifiForUploadPhotos, null, false), - readonlyModeEnabled(StoreKey.readonlyModeEnabled, "readonlyModeEnabled", false), - albumGridView(StoreKey.albumGridView, "albumGridView", false), - backupRequireCharging(StoreKey.backupRequireCharging, null, false), - backupTriggerDelay(StoreKey.backupTriggerDelay, null, 30), - cleanupKeepFavorites(StoreKey.cleanupKeepFavorites, null, true), - cleanupKeepMediaType(StoreKey.cleanupKeepMediaType, null, 0), - cleanupKeepAlbumIds(StoreKey.cleanupKeepAlbumIds, null, ""), - cleanupCutoffDaysAgo(StoreKey.cleanupCutoffDaysAgo, null, -1), - cleanupDefaultsInitialized(StoreKey.cleanupDefaultsInitialized, null, false); + readonlyModeEnabled(StoreKey.readonlyModeEnabled, "readonlyModeEnabled", false); const AppSettingsEnum(this.storeKey, this.hiveKey, this.defaultValue); diff --git a/mobile/lib/services/auth.service.dart b/mobile/lib/services/auth.service.dart index 667681e579..0de22fd124 100644 --- a/mobile/lib/services/auth.service.dart +++ b/mobile/lib/services/auth.service.dart @@ -1,19 +1,19 @@ import 'dart:async'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/settings_key.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/utils/background_sync.dart'; import 'package:immich_mobile/entities/store.entity.dart'; +import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/network.repository.dart'; import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart'; import 'package:immich_mobile/models/auth/login_response.model.dart'; import 'package:immich_mobile/providers/api.provider.dart'; -import 'package:immich_mobile/providers/app_settings.provider.dart'; import 'package:immich_mobile/providers/background_sync.provider.dart'; import 'package:immich_mobile/repositories/auth.repository.dart'; import 'package:immich_mobile/repositories/auth_api.repository.dart'; import 'package:immich_mobile/services/api.service.dart'; -import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/services/network.service.dart'; import 'package:logging/logging.dart'; import 'package:openapi/api.dart'; @@ -25,7 +25,6 @@ final authServiceProvider = Provider( ref.watch(apiServiceProvider), ref.watch(networkServiceProvider), ref.watch(backgroundSyncProvider), - ref.watch(appSettingsServiceProvider), ), ); @@ -35,7 +34,6 @@ class AuthService { final ApiService _apiService; final NetworkService _networkService; final BackgroundSyncManager _backgroundSyncManager; - final AppSettingsService _appSettingsService; final _log = Logger("AuthService"); AuthService( @@ -44,7 +42,6 @@ class AuthService { this._apiService, this._networkService, this._backgroundSyncManager, - this._appSettingsService, ); /// Validates the provided server URL by resolving and setting the endpoint. @@ -103,7 +100,7 @@ class AuthService { _log.severe("Error clearing local data", error, stackTrace); }); - await _appSettingsService.setSetting(AppSettingsEnum.enableBackup, false); + await SettingsRepository.instance.write(SettingsKey.backupEnabled, false); } } @@ -113,7 +110,7 @@ class AuthService { /// - Authentication repository data /// - Current user information /// - Access token - /// - Asset ETag + /// - Server-specific endpoint configuration /// /// All deletions are executed in parallel using [Future.wait]. Future clearLocalData() async { @@ -123,11 +120,12 @@ class AuthService { _authRepository.clearLocalData(), Store.delete(StoreKey.currentUser), Store.delete(StoreKey.accessToken), - Store.delete(StoreKey.assetETag), - Store.delete(StoreKey.autoEndpointSwitching), - Store.delete(StoreKey.preferredWifiName), - Store.delete(StoreKey.localEndpoint), - Store.delete(StoreKey.externalEndpointList), + SettingsRepository.instance.clear(const [ + .networkAutoEndpointSwitching, + .networkPreferredWifiName, + .networkLocalEndpoint, + .networkExternalEndpointList, + ]), ]); } diff --git a/mobile/lib/services/background_upload.service.dart b/mobile/lib/services/background_upload.service.dart index d54a677c24..903fd02395 100644 --- a/mobile/lib/services/background_upload.service.dart +++ b/mobile/lib/services/background_upload.service.dart @@ -13,14 +13,13 @@ import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/platform_extensions.dart'; import 'package:immich_mobile/infrastructure/repositories/backup.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart'; -import 'package:immich_mobile/providers/app_settings.provider.dart'; import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; import 'package:immich_mobile/providers/infrastructure/storage.provider.dart'; import 'package:immich_mobile/repositories/asset_media.repository.dart'; import 'package:immich_mobile/repositories/upload.repository.dart'; import 'package:immich_mobile/services/api.service.dart'; -import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/utils/debug_print.dart'; import 'package:logging/logging.dart'; import 'package:path/path.dart' as p; @@ -31,7 +30,6 @@ final backgroundUploadServiceProvider = Provider((ref) { ref.watch(storageRepositoryProvider), ref.watch(localAssetRepository), ref.watch(backupRepositoryProvider), - ref.watch(appSettingsServiceProvider), ref.watch(assetMediaRepositoryProvider), ); @@ -82,7 +80,9 @@ class UploadTaskMetadata { @override bool operator ==(covariant UploadTaskMetadata other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } return other.localAssetId == localAssetId && other.isLivePhotos == isLivePhotos && @@ -103,7 +103,6 @@ class BackgroundUploadService { this._storageRepository, this._localAssetRepository, this._backupRepository, - this._appSettingsService, this._assetMediaRepository, ) { _uploadRepository.onUploadStatus = _onUploadCallback; @@ -114,7 +113,6 @@ class BackgroundUploadService { final StorageRepository _storageRepository; final DriftLocalAssetRepository _localAssetRepository; final DriftBackupRepository _backupRepository; - final AppSettingsService _appSettingsService; final AssetMediaRepository _assetMediaRepository; final Logger _logger = Logger('BackgroundUploadService'); @@ -361,15 +359,14 @@ class BackgroundUploadService { } bool _shouldRequireWiFi(LocalAsset asset) { - bool requiresWiFi = true; - - if (asset.isVideo && _appSettingsService.getSetting(AppSettingsEnum.useCellularForUploadVideos)) { - requiresWiFi = false; - } else if (!asset.isVideo && _appSettingsService.getSetting(AppSettingsEnum.useCellularForUploadPhotos)) { - requiresWiFi = false; + final backup = SettingsRepository.instance.appConfig.backup; + if (asset.isVideo && backup.useCellularForVideos) { + return false; } - - return requiresWiFi; + if (!asset.isVideo && backup.useCellularForPhotos) { + return false; + } + return true; } Future buildUploadTask( @@ -396,6 +393,7 @@ class BackgroundUploadService { final (baseDirectory, directory, filename) = await Task.split(filePath: file.path); final fieldsMap = { 'filename': originalFileName ?? filename, + // deviceAssetId/deviceId required by server v2.7.5 and below (drop in v4.0 per #27818). 'deviceAssetId': deviceAssetId ?? '', 'deviceId': deviceId, 'fileCreatedAt': createdAt.toUtc().toIso8601String(), diff --git a/mobile/lib/services/deep_link.service.dart b/mobile/lib/services/deep_link.service.dart index 5ff0fa8a4d..26f2fb685b 100644 --- a/mobile/lib/services/deep_link.service.dart +++ b/mobile/lib/services/deep_link.service.dart @@ -45,21 +45,12 @@ class DeepLinkService { this._currentUser, ); - DeepLink _handleColdStart(PageRouteInfo route, bool isColdStart) { - return DeepLink([ - // we need something to segue back to if the app was cold started - // TODO: use MainTimelineRoute this when beta is default - if (isColdStart) const TabShellRoute(), - route, - ]); - } - - Future handleScheme(PlatformDeepLink link, WidgetRef ref, bool isColdStart) async { + Future handleScheme(PlatformDeepLink link, WidgetRef ref) async { // get everything after the scheme, since Uri cannot parse path final intent = link.uri.host; final queryParams = link.uri.queryParameters; - PageRouteInfo? deepLinkRoute = switch (intent) { + return switch (intent) { "memory" => await _buildMemoryDeepLink(queryParams['id'] ?? ''), "asset" => await _buildAssetDeepLink(queryParams['id'] ?? '', ref), "album" => await _buildAlbumDeepLink(queryParams['id'] ?? ''), @@ -67,20 +58,9 @@ class DeepLinkService { "activity" => await _buildActivityDeepLink(queryParams['albumId'] ?? ''), _ => null, }; - - // Deep link resolution failed, safely handle it based on the app state - if (deepLinkRoute == null) { - if (isColdStart) { - return DeepLink.defaultPath; - } - - return DeepLink.none; - } - - return _handleColdStart(deepLinkRoute, isColdStart); } - Future handleMyImmichApp(PlatformDeepLink link, WidgetRef ref, bool isColdStart) async { + Future handleMyImmichApp(PlatformDeepLink link, WidgetRef ref) async { final path = link.uri.path; const uuidRegex = r'[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}'; @@ -88,27 +68,20 @@ class DeepLinkService { final albumRegex = RegExp('/albums/($uuidRegex)'); final peopleRegex = RegExp('/people/($uuidRegex)'); - PageRouteInfo? deepLinkRoute; if (assetRegex.hasMatch(path)) { final assetId = assetRegex.firstMatch(path)?.group(1) ?? ''; - deepLinkRoute = await _buildAssetDeepLink(assetId, ref); - } else if (albumRegex.hasMatch(path)) { + return _buildAssetDeepLink(assetId, ref); + } + if (albumRegex.hasMatch(path)) { final albumId = albumRegex.firstMatch(path)?.group(1) ?? ''; - deepLinkRoute = await _buildAlbumDeepLink(albumId); - } else if (peopleRegex.hasMatch(path)) { + return _buildAlbumDeepLink(albumId); + } + if (peopleRegex.hasMatch(path)) { final peopleId = peopleRegex.firstMatch(path)?.group(1) ?? ''; - deepLinkRoute = await _buildPeopleDeepLink(peopleId); - } else if (path == "/memory") { - deepLinkRoute = await _buildMemoryDeepLink(null); + return _buildPeopleDeepLink(peopleId); } - // Deep link resolution failed, safely handle it based on the app state - if (deepLinkRoute == null) { - if (isColdStart) return DeepLink.defaultPath; - return DeepLink.none; - } - - return _handleColdStart(deepLinkRoute, isColdStart); + return null; } Future _buildMemoryDeepLink(String? memoryId) async { diff --git a/mobile/lib/services/folder.service.dart b/mobile/lib/services/folder.service.dart index bf7590ce54..543c7231d6 100644 --- a/mobile/lib/services/folder.service.dart +++ b/mobile/lib/services/folder.service.dart @@ -21,7 +21,9 @@ class FolderService { Map> folderMap = {}; for (String fullPath in paths) { - if (fullPath == '/') continue; + if (fullPath == '/') { + continue; + } // Ensure the path starts with a slash if (!fullPath.startsWith('/')) { diff --git a/mobile/lib/services/foreground_upload.service.dart b/mobile/lib/services/foreground_upload.service.dart index ce02c9c56b..aea187dc9f 100644 --- a/mobile/lib/services/foreground_upload.service.dart +++ b/mobile/lib/services/foreground_upload.service.dart @@ -7,18 +7,17 @@ import 'package:immich_mobile/domain/models/asset/asset_metadata.model.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/entities/store.entity.dart'; -import 'package:immich_mobile/extensions/platform_extensions.dart'; import 'package:immich_mobile/extensions/network_capability_extensions.dart'; +import 'package:immich_mobile/extensions/platform_extensions.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/infrastructure/repositories/backup.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart'; import 'package:immich_mobile/platform/connectivity_api.g.dart'; -import 'package:immich_mobile/providers/app_settings.provider.dart'; import 'package:immich_mobile/providers/infrastructure/platform.provider.dart'; import 'package:immich_mobile/providers/infrastructure/storage.provider.dart'; import 'package:immich_mobile/repositories/asset_media.repository.dart'; import 'package:immich_mobile/repositories/upload.repository.dart'; -import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:logging/logging.dart'; import 'package:path/path.dart' as p; import 'package:photo_manager/photo_manager.dart' show PMProgressHandler; @@ -39,7 +38,6 @@ final foregroundUploadServiceProvider = Provider((ref) { ref.watch(storageRepositoryProvider), ref.watch(backupRepositoryProvider), ref.watch(connectivityApiProvider), - ref.watch(appSettingsServiceProvider), ref.watch(assetMediaRepositoryProvider), ); }); @@ -55,7 +53,6 @@ class ForegroundUploadService { this._storageRepository, this._backupRepository, this._connectivityApi, - this._appSettingsService, this._assetMediaRepository, ); @@ -63,7 +60,6 @@ class ForegroundUploadService { final StorageRepository _storageRepository; final DriftBackupRepository _backupRepository; final ConnectivityApi _connectivityApi; - final AppSettingsService _appSettingsService; final AssetMediaRepository _assetMediaRepository; final Logger _logger = Logger('ForegroundUploadService'); @@ -155,7 +151,7 @@ class ForegroundUploadService { List files, { Completer? cancelToken, void Function(String fileId, int bytes, int totalBytes)? onProgress, - void Function(String fileId)? onSuccess, + void Function(String fileId, String remoteAssetId)? onSuccess, void Function(String fileId, String errorMessage)? onError, }) async { if (files.isEmpty) { @@ -175,7 +171,7 @@ class ForegroundUploadService { ); if (result.isSuccess) { - onSuccess?.call(fileId); + onSuccess?.call(fileId, result.remoteAssetId!); } else if (!result.isCancelled && result.errorMessage != null) { onError?.call(fileId, result.errorMessage!); } @@ -324,12 +320,13 @@ class ForegroundUploadService { final deviceId = Store.get(StoreKey.deviceId); final fields = { + // deviceAssetId/deviceId required by server v2.7.5 and below (drop in v4.0 per #27818). 'deviceAssetId': asset.localId!, 'deviceId': deviceId, 'fileCreatedAt': asset.createdAt.toUtc().toIso8601String(), 'fileModifiedAt': asset.updatedAt.toUtc().toIso8601String(), 'isFavorite': asset.isFavorite.toString(), - 'duration': asset.duration.toString(), + 'duration': (asset.durationMs ?? 0).toString(), }; // Upload live photo video first if available @@ -431,6 +428,7 @@ class ForegroundUploadService { final filename = p.basename(file.path); final fields = { + // deviceAssetId/deviceId required by server v2.7.5 and below (drop in v4.0 per #27818). 'deviceAssetId': deviceAssetId, 'deviceId': Store.get(StoreKey.deviceId), 'fileCreatedAt': fileCreatedAt.toUtc().toIso8601String(), @@ -453,14 +451,13 @@ class ForegroundUploadService { } bool _shouldRequireWiFi(LocalAsset asset) { - bool requiresWiFi = true; - - if (asset.isVideo && _appSettingsService.getSetting(AppSettingsEnum.useCellularForUploadVideos)) { - requiresWiFi = false; - } else if (!asset.isVideo && _appSettingsService.getSetting(AppSettingsEnum.useCellularForUploadPhotos)) { - requiresWiFi = false; + final backup = SettingsRepository.instance.appConfig.backup; + if (asset.isVideo && backup.useCellularForVideos) { + return false; } - - return requiresWiFi; + if (!asset.isVideo && backup.useCellularForPhotos) { + return false; + } + return true; } } diff --git a/mobile/lib/services/local_files_manager.service.dart b/mobile/lib/services/local_files_manager.service.dart deleted file mode 100644 index 0cc00f3e4b..0000000000 --- a/mobile/lib/services/local_files_manager.service.dart +++ /dev/null @@ -1,66 +0,0 @@ -import 'package:flutter/services.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:logging/logging.dart'; - -final localFileManagerServiceProvider = Provider((ref) => const LocalFilesManagerService()); - -class LocalFilesManagerService { - const LocalFilesManagerService(); - - static final Logger _logger = Logger('LocalFilesManager'); - static const MethodChannel _channel = MethodChannel('file_trash'); - - Future moveToTrash(List mediaUrls) async { - try { - return await _channel.invokeMethod('moveToTrash', {'mediaUrls': mediaUrls}); - } catch (e, s) { - _logger.warning('Error moving file to trash', e, s); - return false; - } - } - - Future restoreFromTrash(String fileName, int type) async { - try { - return await _channel.invokeMethod('restoreFromTrash', {'fileName': fileName, 'type': type}); - } catch (e, s) { - _logger.warning('Error restore file from trash', e, s); - return false; - } - } - - Future restoreFromTrashById(String mediaId, int type) async { - try { - return await _channel.invokeMethod('restoreFromTrash', {'mediaId': mediaId, 'type': type}); - } catch (e, s) { - _logger.warning('Error restore file from trash by Id', e, s); - return false; - } - } - - Future requestManageMediaPermission() async { - try { - return await _channel.invokeMethod('requestManageMediaPermission'); - } catch (e, s) { - _logger.warning('Error requesting manage media permission', e, s); - return false; - } - } - - Future hasManageMediaPermission() async { - try { - return await _channel.invokeMethod('hasManageMediaPermission'); - } catch (e, s) { - _logger.warning('Error requesting manage media permission state', e, s); - return false; - } - } - - Future manageMediaPermission() async { - try { - return await _channel.invokeMethod('manageMediaPermission'); - } catch (e, s) { - _logger.warning('Error requesting manage media permission settings', e, s); - return false; - } - } -} diff --git a/mobile/lib/services/oauth.service.dart b/mobile/lib/services/oauth.service.dart index 99ceca3229..d8b1604e00 100644 --- a/mobile/lib/services/oauth.service.dart +++ b/mobile/lib/services/oauth.service.dart @@ -18,7 +18,11 @@ class OAuthService { log.info("Starting OAuth flow with redirect URI: $redirectUri"); final dto = await _apiService.oAuthApi.startOAuth( - OAuthConfigDto(redirectUri: redirectUri, state: state, codeChallenge: codeChallenge), + OAuthConfigDto( + redirectUri: redirectUri, + state: Optional.present(state), + codeChallenge: Optional.present(codeChallenge), + ), ); final authUrl = dto?.url; @@ -37,7 +41,7 @@ class OAuthService { } return await _apiService.oAuthApi.finishOAuth( - OAuthCallbackDto(url: result, state: state, codeVerifier: codeVerifier), + OAuthCallbackDto(url: result, state: Optional.present(state), codeVerifier: Optional.present(codeVerifier)), ); } } diff --git a/mobile/lib/services/shared_link.service.dart b/mobile/lib/services/shared_link.service.dart index 46e83f0fc4..975070f2a0 100644 --- a/mobile/lib/services/shared_link.service.dart +++ b/mobile/lib/services/shared_link.service.dart @@ -48,26 +48,26 @@ class SharedLinkService { if (type == SharedLinkType.ALBUM) { dto = SharedLinkCreateDto( type: type, - albumId: albumId, - showMetadata: showMeta, - allowDownload: allowDownload, - allowUpload: allowUpload, - expiresAt: expiresAt, - description: description, - password: password, - slug: slug, + albumId: albumId == null ? const Optional.absent() : Optional.present(albumId), + showMetadata: Optional.present(showMeta), + allowDownload: Optional.present(allowDownload), + allowUpload: Optional.present(allowUpload), + expiresAt: expiresAt == null ? const Optional.absent() : Optional.present(expiresAt), + description: description == null ? const Optional.absent() : Optional.present(description), + password: password == null ? const Optional.absent() : Optional.present(password), + slug: slug == null ? const Optional.absent() : Optional.present(slug), ); } else if (assetIds != null) { dto = SharedLinkCreateDto( type: type, - showMetadata: showMeta, - allowDownload: allowDownload, - allowUpload: allowUpload, - expiresAt: expiresAt, - description: description, - password: password, - slug: slug, - assetIds: assetIds, + showMetadata: Optional.present(showMeta), + allowDownload: Optional.present(allowDownload), + allowUpload: Optional.present(allowUpload), + expiresAt: expiresAt == null ? const Optional.absent() : Optional.present(expiresAt), + description: description == null ? const Optional.absent() : Optional.present(description), + password: password == null ? const Optional.absent() : Optional.present(password), + slug: slug == null ? const Optional.absent() : Optional.present(slug), + assetIds: Optional.present(assetIds), ); } @@ -88,24 +88,22 @@ class SharedLinkService { required bool? showMeta, required bool? allowDownload, required bool? allowUpload, - bool? changeExpiry = false, - String? description, - String? password, + Optional password = const Optional.absent(), + Optional description = const Optional.absent(), String? slug, - DateTime? expiresAt, + Optional expiresAt = const Optional.absent(), }) async { try { final responseDto = await _apiService.sharedLinksApi.updateSharedLink( id, SharedLinkEditDto( - showMetadata: showMeta, - allowDownload: allowDownload, - allowUpload: allowUpload, - expiresAt: expiresAt, - description: description, + showMetadata: showMeta == null ? const Optional.absent() : Optional.present(showMeta), + allowDownload: allowDownload == null ? const Optional.absent() : Optional.present(allowDownload), + allowUpload: allowUpload == null ? const Optional.absent() : Optional.present(allowUpload), password: password, - slug: slug, - changeExpiryTime: changeExpiry, + description: description, + expiresAt: expiresAt, + slug: slug == null ? const Optional.absent() : Optional.present(slug), ), ); if (responseDto != null) { diff --git a/mobile/lib/services/view_intent.service.dart b/mobile/lib/services/view_intent.service.dart new file mode 100644 index 0000000000..22a3407e5a --- /dev/null +++ b/mobile/lib/services/view_intent.service.dart @@ -0,0 +1,108 @@ +import 'dart:io'; + +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/platform/view_intent_api.g.dart'; +import 'package:path/path.dart' as p; +import 'package:path_provider/path_provider.dart'; + +final viewIntentServiceProvider = Provider((ref) => ViewIntentService(ViewIntentHostApi())); + +class ViewIntentService { + final ViewIntentHostApi _viewIntentHostApi; + final Future Function() _temporaryDirectory; + String? _managedTempFilePath; + final Set _activeUploadPaths = {}; + + ViewIntentService(this._viewIntentHostApi, {Future Function()? temporaryDirectory}) + : _temporaryDirectory = temporaryDirectory ?? getTemporaryDirectory; + + Future consumeViewIntent() async { + try { + return await _viewIntentHostApi.consumeViewIntent(); + } catch (_) { + // Ignore errors - view intent might not be present + return null; + } + } + + Future setManagedTempFilePath(String path) async { + final previous = _managedTempFilePath; + if (previous == path) { + return; + } + _managedTempFilePath = path; + if (previous != null) { + await cleanupTempFile(previous); + } + } + + Future cleanupManagedTempFile() async { + final path = _managedTempFilePath; + _managedTempFilePath = null; + if (path != null) { + await cleanupTempFile(path); + } + } + + Future cleanupManagedTempFileIfCurrent(String path) async { + if (_managedTempFilePath == path) { + _managedTempFilePath = null; + } + await cleanupTempFile(path); + } + + Future cleanupTempFile(String path) async { + if (!_isManagedTempFile(path)) { + return; + } + if (_activeUploadPaths.contains(path)) { + return; + } + + try { + final file = File(path); + if (await file.exists()) { + await file.delete(); + } + } catch (_) { + // Best-effort cleanup only. + } + } + + Future cleanupStaleTempFiles() async { + try { + final tempDirectory = await _temporaryDirectory(); + await for (final entity in tempDirectory.list()) { + if (entity is! File) { + continue; + } + + final path = entity.path; + if (!_isManagedTempFile(path) || path == _managedTempFilePath || _activeUploadPaths.contains(path)) { + continue; + } + + await entity.delete(); + } + } catch (_) { + // Best-effort cleanup only. + } + } + + void markUploadActive(String path) { + _activeUploadPaths.add(path); + } + + Future markUploadInactive(String path) async { + if (!_activeUploadPaths.remove(path)) { + return; + } + if (_managedTempFilePath != path) { + await cleanupTempFile(path); + } + } + + bool _isManagedTempFile(String path) { + return p.basename(path).startsWith('view_intent_') && p.basename(p.dirname(path)) == 'cache'; + } +} diff --git a/mobile/lib/services/view_intent_asset_resolver.service.dart b/mobile/lib/services/view_intent_asset_resolver.service.dart new file mode 100644 index 0000000000..7bda1bdc13 --- /dev/null +++ b/mobile/lib/services/view_intent_asset_resolver.service.dart @@ -0,0 +1,65 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; +import 'package:immich_mobile/domain/services/timeline.service.dart'; +import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart'; +import 'package:immich_mobile/models/view_intent/view_intent_payload.extension.dart'; +import 'package:immich_mobile/platform/view_intent_api.g.dart'; +import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; +import 'package:logging/logging.dart'; + +class ViewIntentResolvedAsset { + final BaseAsset asset; + final TimelineService timelineService; + + final String? viewIntentFilePath; + + const ViewIntentResolvedAsset({required this.asset, required this.timelineService, this.viewIntentFilePath}); +} + +final viewIntentAssetResolverProvider = Provider( + (ref) => ViewIntentAssetResolver( + localAssetRepository: ref.read(localAssetRepository), + timelineFactory: ref.read(timelineFactoryProvider), + ), +); + +class ViewIntentAssetResolver { + final DriftLocalAssetRepository _localAssetRepository; + final TimelineFactory _timelineFactory; + static final Logger _logger = Logger('ViewIntentAssetResolver'); + + const ViewIntentAssetResolver({required this._localAssetRepository, required this._timelineFactory}); + + Future resolve(ViewIntentPayload attachment) async { + final localAssetId = attachment.localAssetId; + final path = attachment.path; + _logger.fine('resolve start, localAssetId=$localAssetId, path=$path, mimeType=${attachment.mimeType}'); + + if (localAssetId == null && path == null) { + throw StateError('ViewIntent resolution requires either a localAssetId or a materialized file path.'); + } + + final localAsset = localAssetId != null ? await _localAssetRepository.getById(localAssetId) : null; + final asset = localAsset ?? _toTransientAsset(attachment); + + return ViewIntentResolvedAsset( + asset: asset, + timelineService: _timelineFactory.fromAssets([asset], TimelineOrigin.deepLink), + viewIntentFilePath: localAsset == null ? path : null, + ); + } + + LocalAsset _toTransientAsset(ViewIntentPayload attachment) { + final now = DateTime.now(); + return LocalAsset( + id: attachment.localAssetId ?? '-${attachment.path!.hashCode.abs()}', + name: attachment.fileName, + type: attachment.isVideo ? AssetType.video : AssetType.image, + createdAt: now, + updatedAt: now, + isEdited: false, + playbackStyle: attachment.playbackStyle, + ); + } +} diff --git a/mobile/lib/services/widget.service.dart b/mobile/lib/services/widget.service.dart index 23ec0aa770..91f65dd684 100644 --- a/mobile/lib/services/widget.service.dart +++ b/mobile/lib/services/widget.service.dart @@ -12,7 +12,7 @@ class WidgetService { const WidgetService(this._repository); Future writeCredentials(String serverURL, String sessionKey, String? customHeaders) async { - await _repository.setAppGroupId(appShareGroupId); + await _repository.setAppGroupId(); await _repository.saveData(kWidgetServerEndpoint, serverURL); await _repository.saveData(kWidgetAuthToken, sessionKey); @@ -25,7 +25,7 @@ class WidgetService { } Future clearCredentials() async { - await _repository.setAppGroupId(appShareGroupId); + await _repository.setAppGroupId(); await _repository.saveData(kWidgetServerEndpoint, ""); await _repository.saveData(kWidgetAuthToken, ""); await _repository.saveData(kWidgetCustomHeaders, ""); diff --git a/mobile/lib/utils/action_button.utils.dart b/mobile/lib/utils/action_button.utils.dart index 4f7ad83093..b9cff613fd 100644 --- a/mobile/lib/utils/action_button.utils.dart +++ b/mobile/lib/utils/action_button.utils.dart @@ -21,11 +21,13 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/move_to_lock_f import 'package:immich_mobile/presentation/widgets/action_buttons/open_in_browser_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/remove_from_album_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/remove_from_lock_folder_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/restore_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/set_album_cover.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/set_profile_picture_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/share_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/share_link_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/similar_photos_action_button.widget.dart'; -import 'package:immich_mobile/presentation/widgets/action_buttons/set_profile_picture_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/slideshow_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/trash_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/unarchive_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/unstack_action_button.widget.dart'; @@ -44,7 +46,6 @@ class ActionButtonContext { final ActionSource source; final bool isCasting; final TimelineOrigin timelineOrigin; - final ThemeData? originalTheme; final int selectedCount; const ActionButtonContext({ @@ -59,7 +60,6 @@ class ActionButtonContext { required this.source, this.isCasting = false, this.timelineOrigin = TimelineOrigin.main, - this.originalTheme, this.selectedCount = 1, }); } @@ -74,6 +74,7 @@ enum ActionButtonType { similarPhotos, setProfilePicture, viewInTimeline, + slideshow, download, upload, openInBrowser, @@ -83,6 +84,7 @@ enum ActionButtonType { moveToLockFolder, removeFromLockFolder, removeFromAlbum, + restoreTrash, trash, deleteLocal, deletePermanent, @@ -114,12 +116,17 @@ enum ActionButtonType { context.isOwner && // !context.isInLockedView && // context.asset.hasRemote && // - context.isTrashEnabled, + context.isTrashEnabled && // + context.timelineOrigin != TimelineOrigin.trash, + ActionButtonType.restoreTrash => + context.isOwner && // + !context.isInLockedView && // + context.asset.hasRemote && // + context.timelineOrigin == TimelineOrigin.trash, ActionButtonType.deletePermanent => context.isOwner && // - context.asset.hasRemote && // - !context.isTrashEnabled || - context.isInLockedView, + context.asset.hasRemote && // + (!context.isTrashEnabled || context.timelineOrigin == TimelineOrigin.trash || context.isInLockedView), ActionButtonType.delete => context.isOwner && // !context.isInLockedView && // @@ -174,6 +181,7 @@ enum ActionButtonType { context.timelineOrigin != TimelineOrigin.localAlbum && context.isOwner, ActionButtonType.cast => context.isCasting || context.asset.hasRemote, + ActionButtonType.slideshow => true, }; } @@ -195,6 +203,7 @@ enum ActionButtonType { iconOnly: iconOnly, menuItem: menuItem, ), + ActionButtonType.slideshow => SlideshowActionButton(iconOnly: iconOnly, menuItem: menuItem), ActionButtonType.archive => ArchiveActionButton(source: context.source, iconOnly: iconOnly, menuItem: menuItem), ActionButtonType.unarchive => UnArchiveActionButton( source: context.source, @@ -203,6 +212,11 @@ enum ActionButtonType { ), ActionButtonType.download => DownloadActionButton(source: context.source, iconOnly: iconOnly, menuItem: menuItem), ActionButtonType.trash => TrashActionButton(source: context.source, iconOnly: iconOnly, menuItem: menuItem), + ActionButtonType.restoreTrash => RestoreActionButton( + source: context.source, + iconOnly: iconOnly, + menuItem: menuItem, + ), ActionButtonType.deletePermanent => DeletePermanentActionButton( source: context.source, iconOnly: iconOnly, @@ -244,7 +258,6 @@ enum ActionButtonType { origin: context.timelineOrigin, iconOnly: iconOnly, menuItem: menuItem, - iconColor: context.originalTheme?.iconTheme.color, ), ActionButtonType.similarPhotos => SimilarPhotosActionButton( assetId: (context.asset as RemoteAsset).id, @@ -259,14 +272,12 @@ enum ActionButtonType { ActionButtonType.openInfo => BaseActionButton( label: 'info'.tr(), iconData: Icons.info_outline, - iconColor: context.originalTheme?.iconTheme.color, menuItem: true, onPressed: () => EventStream.shared.emit(const ViewerShowDetailsEvent()), ), ActionButtonType.viewInTimeline => BaseActionButton( label: 'view_in_timeline'.tr(), iconData: Icons.image_search, - iconColor: context.originalTheme?.iconTheme.color, iconOnly: iconOnly, menuItem: menuItem, onPressed: buildContext == null @@ -297,6 +308,7 @@ enum ActionButtonType { ActionButtonType.moveToLockFolder => 10, ActionButtonType.deleteLocal => 10, ActionButtonType.delete => 10, + ActionButtonType.restoreTrash => 10, // 90: advancedInfo ActionButtonType.advancedInfo => 90, // 1: others @@ -314,6 +326,8 @@ class ActionButtonBuilder { ActionButtonType.delete, ActionButtonType.archive, ActionButtonType.unarchive, + ActionButtonType.restoreTrash, + ActionButtonType.deletePermanent, }; static List build(ActionButtonContext context) { diff --git a/mobile/lib/utils/bootstrap.dart b/mobile/lib/utils/bootstrap.dart index e79b06f53b..9bd652381a 100644 --- a/mobile/lib/utils/bootstrap.dart +++ b/mobile/lib/utils/bootstrap.dart @@ -6,6 +6,7 @@ import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/log.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/logger_db.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/network.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/store.repository.dart'; import 'package:photo_manager/photo_manager.dart'; @@ -48,9 +49,11 @@ abstract final class Bootstrap { await StoreService.init(storeRepository: storeRepo, listenUpdates: listenStoreUpdates); + final settingsRepo = await SettingsRepository.ensureInitialized(drift); + await LogService.init( logRepository: LogRepository(logDb), - storeRepository: storeRepo, + settingsRepository: settingsRepo, shouldBuffer: shouldBufferLogs, ); diff --git a/mobile/lib/utils/bytes_units.dart b/mobile/lib/utils/bytes_units.dart index 66de6493ab..5eb15221fe 100644 --- a/mobile/lib/utils/bytes_units.dart +++ b/mobile/lib/utils/bytes_units.dart @@ -18,7 +18,9 @@ String formatBytes(int bytes) { } String formatHumanReadableBytes(int bytes, int decimals) { - if (bytes <= 0) return "0 B"; + if (bytes <= 0) { + return "0 B"; + } const suffixes = ["B", "KiB", "MiB", "GiB", "TiB"]; var i = (log(bytes) / log(1024)).floor(); return '${(bytes / pow(1024, i)).toStringAsFixed(decimals)} ${suffixes[i]}'; diff --git a/mobile/lib/utils/hooks/app_settings_update_hook.dart b/mobile/lib/utils/hooks/app_settings_update_hook.dart index 954e44229a..c498b60b06 100644 --- a/mobile/lib/utils/hooks/app_settings_update_hook.dart +++ b/mobile/lib/utils/hooks/app_settings_update_hook.dart @@ -1,7 +1,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter_hooks/flutter_hooks.dart' hide Store; -import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/entities/store.entity.dart'; +import 'package:immich_mobile/services/app_settings.service.dart'; ValueNotifier useAppSettingsState(AppSettingsEnum key) { final notifier = useState(Store.get(key.storeKey, key.defaultValue)); diff --git a/mobile/lib/utils/isolate.dart b/mobile/lib/utils/isolate.dart index 20b56d4875..ab3b19b78f 100644 --- a/mobile/lib/utils/isolate.dart +++ b/mobile/lib/utils/isolate.dart @@ -8,10 +8,9 @@ import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/providers/infrastructure/cancel.provider.dart'; import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; import 'package:immich_mobile/utils/bootstrap.dart'; -import 'package:immich_mobile/utils/debug_print.dart'; import 'package:immich_mobile/wm_executor.dart'; import 'package:logging/logging.dart'; -import 'package:worker_manager/worker_manager.dart'; +import 'package:worker_manager/worker_manager.dart' show Cancelable; class InvalidIsolateUsageException implements Exception { const InvalidIsolateUsageException(); @@ -30,50 +29,27 @@ Cancelable runInIsolateGentle({ throw const InvalidIsolateUsageException(); } - return workerManagerPatch.executeGentle((cancelledChecker) async { - T? result; - await runZonedGuarded( - () async { - BackgroundIsolateBinaryMessenger.ensureInitialized(token); - DartPluginRegistrant.ensureInitialized(); + return workerManagerPatch.executeGentle((onCancel) async { + BackgroundIsolateBinaryMessenger.ensureInitialized(token); + DartPluginRegistrant.ensureInitialized(); - final (drift, logDb) = await Bootstrap.initDomain(shouldBufferLogs: false, listenStoreUpdates: false); - final ref = ProviderContainer( - overrides: [ - cancellationProvider.overrideWithValue(cancelledChecker), - driftProvider.overrideWith(driftOverride(drift)), - ], - ); - - Logger log = Logger("IsolateLogger"); - - try { - result = await computation(ref); - } on CanceledError { - log.warning("Computation cancelled ${debugLabel == null ? '' : ' for $debugLabel'}"); - } catch (error, stack) { - log.severe("Error in runInIsolateGentle ${debugLabel == null ? '' : ' for $debugLabel'}", error, stack); - } finally { - try { - ref.dispose(); - - await Store.dispose(); - await LogService.I.dispose(); - await logDb.close(); - await drift.close(); - } catch (error, stack) { - dPrint(() => "Error closing resources in isolate: $error, $stack"); - } finally { - ref.dispose(); - // Delay to ensure all resources are released - await Future.delayed(const Duration(seconds: 2)); - } - } - }, - (error, stack) { - dPrint(() => "Error in isolate $debugLabel zone: $error, $stack"); - }, + final log = Logger("IsolateLogger"); + final (drift, logDb) = await Bootstrap.initDomain(shouldBufferLogs: false, listenStoreUpdates: false); + final ref = ProviderContainer( + overrides: [cancellationProvider.overrideWithValue(onCancel), driftProvider.overrideWith(driftOverride(drift))], ); - return result; + + try { + return await computation(ref); + } catch (error, stack) { + log.severe("Error in runInIsolateGentle${debugLabel == null ? '' : ' for $debugLabel'}", error, stack); + return null; + } finally { + ref.dispose(); + await Store.dispose(); + await LogService.I.dispose(); + await logDb.close(); + await drift.close(); + } }); } diff --git a/mobile/lib/utils/isolate_worker.dart b/mobile/lib/utils/isolate_worker.dart new file mode 100644 index 0000000000..60048c2c81 --- /dev/null +++ b/mobile/lib/utils/isolate_worker.dart @@ -0,0 +1,163 @@ +// Forked from worker_manager's `WorkerImpl` (src/worker/worker_io.dart): a +// `CancelRequest` completes the computation's [Completer] (so it can await +// cancellation and unwind) instead of flipping a polled flag, and [shutdown] +// lets the isolate drain and exit on its own rather than force-killing it. Only +// the gentle-with-cancellation path immich uses is kept. +// +// ignore_for_file: implementation_imports + +import 'dart:async'; +import 'dart:isolate'; + +import 'package:worker_manager/src/scheduling/task.dart'; +import 'package:worker_manager/src/worker/cancel_request.dart'; +import 'package:worker_manager/src/worker/result.dart'; + +/// A worker computation that receives a [Completer] which completes on +/// cancellation: await its future to react promptly, or read `isCompleted`. +typedef GentleExecution = FutureOr Function(Completer onCancel); + +class _Shutdown { + const _Shutdown(); +} + +class IsolateWorker { + IsolateWorker(); + + Isolate? _isolate; + RawReceivePort? _receivePort; + SendPort? _sendPort; + Completer? _sendPortReceived; + Completer? _result; + + String? taskId; + + bool get initialized => _sendPortReceived?.isCompleted ?? false; + + bool get initializing { + final sendPortReceived = _sendPortReceived; + return sendPortReceived != null && !sendPortReceived.isCompleted; + } + + Future initialize() async { + final sendPortReceived = _sendPortReceived = Completer(); + final receivePort = _receivePort = RawReceivePort(); + receivePort.handler = (Object message) { + if (message is SendPort) { + _sendPort = message; + sendPortReceived.complete(); + } else if (message is ResultSuccess) { + _result?.complete(message.value); + _afterTask(); + } else if (message is ResultError) { + _result?.completeError(message.error, message.stackTrace); + _afterTask(); + } + }; + _isolate = await Isolate.spawn(_isolateEntry, receivePort.sendPort, errorsAreFatal: false); + await sendPortReceived.future; + } + + Future work(Task task) async { + taskId = task.id; + final result = _result = Completer(); + _sendPort!.send(task.execution); + return await (result.future as Future); + } + + /// Cancels the current task without retiring the worker. + void cancelGentle() => _sendPort?.send(CancelRequest()); + + /// Cancels any in-flight task and awaits the isolate exiting on its own โ€” no + /// force-kill, so `finally` blocks and native cleanup always run. + /// + /// Detaches the slot up front so a concurrent [initialize] can revive it + /// without colliding (revival installs fresh ports while this drains the ones + /// it captured locally). A revived worker is always idle, so the still-live + /// receive-port handler can't misroute a result. + Future shutdown() async { + final sendPortReceived = _sendPortReceived; + if (sendPortReceived != null && !sendPortReceived.isCompleted) { + await sendPortReceived.future; + } + + final isolate = _isolate; + final receivePort = _receivePort; + final sendPort = _sendPort; + if (isolate == null || receivePort == null || sendPort == null) { + return; + } + _isolate = null; + _sendPort = null; + _sendPortReceived = null; + // Not _result: an in-flight task still delivers it before exiting; nulling + // here would drop that and hang work()'s caller. + + final exited = Completer(); + final exitPort = RawReceivePort(); + exitPort.handler = (_) { + if (!exited.isCompleted) { + exited.complete(); + } + exitPort.close(); + }; + isolate.addOnExitListener(exitPort.sendPort); + sendPort.send(const _Shutdown()); + await exited.future; + receivePort.close(); + } + + void _afterTask() { + taskId = null; + _result = null; + } + + static void _isolateEntry(SendPort sendPort) { + final receivePort = RawReceivePort(); + sendPort.send(receivePort.sendPort); + // One task at a time, so a single completer suffices; null between tasks. + Completer? onCancel; + void cancel() { + if (onCancel?.isCompleted == false) { + onCancel!.complete(); + } + } + + var shuttingDown = false; + var running = false; + receivePort.handler = (message) async { + if (message is _Shutdown) { + shuttingDown = true; + cancel(); + if (!running) { + Isolate.exit(); + } + return; + } + if (message is CancelRequest) { + cancel(); + return; + } + final execution = message as GentleExecution; + onCancel = Completer(); + running = true; + Result result; + try { + result = ResultSuccess(await execution(onCancel!)); + } catch (error, stackTrace) { + result = ResultError(error, stackTrace); + } finally { + onCancel = null; + running = false; + } + if (shuttingDown) { + // An isolate that has used platform channels can't exit on its own (Flutter's BackgroundIsolateBinaryMessenger + // opens an undisposable port), so closing our ports isn't enough. Isolate.exit delivers the result as its final + // message and terminates. It's abrupt (skips pending finally/microtasks) but safe here: the computation and its + // `finally` are already done and there's no await before this, so nothing pending is skipped. + Isolate.exit(sendPort, result); + } + sendPort.send(result); + }; + } +} diff --git a/mobile/lib/utils/migration.dart b/mobile/lib/utils/migration.dart index 9ac805af39..3afa554e29 100644 --- a/mobile/lib/utils/migration.dart +++ b/mobile/lib/utils/migration.dart @@ -1,25 +1,305 @@ import 'dart:async'; +import 'dart:convert'; +import 'package:collection/collection.dart'; +import 'package:drift/drift.dart'; +import 'package:flutter/material.dart'; +import 'package:immich_mobile/constants/colors.dart'; +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/domain/models/config/app_config.dart'; +import 'package:immich_mobile/domain/models/log.model.dart'; +import 'package:immich_mobile/domain/models/settings_key.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/domain/models/timeline.model.dart'; import 'package:immich_mobile/entities/store.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/settings.entity.drift.dart'; +import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/network.repository.dart'; -import 'package:immich_mobile/services/api.service.dart'; +import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart'; +import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart'; -const int targetVersion = 25; +const int targetVersion = 26; -Future migrateDatabaseIfNeeded() async { +Future migrateDatabaseIfNeeded(Drift drift) async { final int version = Store.get(StoreKey.version, targetVersion); if (version < 25) { - final accessToken = Store.tryGet(StoreKey.accessToken); - if (accessToken != null && accessToken.isNotEmpty) { - final serverUrls = ApiService.getServerUrls(); - if (serverUrls.isNotEmpty) { - await NetworkRepository.setHeaders(ApiService.getRequestHeaders(), serverUrls, token: accessToken); - } - } + await _migrateTo25(); + } + + if (version < 26) { + await _migrateTo26(drift); } await Store.put(StoreKey.version, targetVersion); return; } + +Future _migrateTo25() async { + final accessToken = Store.tryGet(StoreKey.accessToken); + if (accessToken == null || accessToken.isEmpty) { + return; + } + + final urls = []; + final serverEndpoint = Store.tryGet(StoreKey.serverEndpoint); + if (serverEndpoint != null && serverEndpoint.isNotEmpty) { + urls.add(serverEndpoint); + } + final localEndpoint = Store.tryGet(StoreKey.legacyLocalEndpoint); + if (localEndpoint != null && localEndpoint.isNotEmpty) { + urls.add(localEndpoint); + } + final externalJson = Store.tryGet(StoreKey.legacyExternalEndpointList); + if (externalJson != null) { + final List list = jsonDecode(externalJson); + for (final entry in list) { + final url = AuxilaryEndpoint.fromJson(entry).url; + if (url.isNotEmpty) { + urls.add(url); + } + } + } + if (urls.isEmpty) { + return; + } + + final customHeadersStr = Store.get(StoreKey.legacyCustomHeaders, ""); + final headers = customHeadersStr.isEmpty + ? const {} + : (jsonDecode(customHeadersStr) as Map).cast(); + + await NetworkRepository.setHeaders(headers, urls, token: accessToken); +} + +Future _migrateTo26(Drift drift) async { + final migrator = _StoreMigrator(drift); + await migrator.migrateEnumIndex(StoreKey.legacyLogLevel, SettingsKey.logLevel, LogLevel.values); + // Theme + await migrator.migrateEnumName(StoreKey.legacyThemeMode, SettingsKey.themeMode, ThemeMode.values); + await migrator.migrateEnumName(StoreKey.legacyPrimaryColor, SettingsKey.themePrimaryColor, ImmichColorPreset.values); + await migrator.migrateBool(StoreKey.legacyDynamicTheme, SettingsKey.themeDynamic); + await migrator.migrateBool(StoreKey.legacyColorfulInterface, SettingsKey.themeColorfulInterface); + // Cleanup + final cleanupKeepAlbumIds = await migrator.readLegacyStoreString(StoreKey.legacyCleanupKeepAlbumIds.id); + if (cleanupKeepAlbumIds != null) { + final ids = cleanupKeepAlbumIds.split(',').where((id) => id.isNotEmpty).toList(); + migrator.stage(StoreKey.legacyCleanupKeepAlbumIds, SettingsKey.cleanupKeepAlbumIds, ids); + } + await migrator.migrateBool(StoreKey.legacyCleanupKeepFavorites, SettingsKey.cleanupKeepFavorites); + await migrator.migrateEnumIndex( + StoreKey.legacyCleanupKeepMediaType, + SettingsKey.cleanupKeepMediaType, + AssetKeepType.values, + ); + await migrator.migrateInt(StoreKey.legacyCleanupCutoffDaysAgo, SettingsKey.cleanupCutoffDaysAgo); + await migrator.migrateBool(StoreKey.legacyCleanupDefaultsInitialized, SettingsKey.cleanupDefaultsInitialized); + // Map + await migrator.migrateBool(StoreKey.legacyMapShowFavoriteOnly, SettingsKey.mapShowFavoriteOnly); + await migrator.migrateInt(StoreKey.legacyMapRelativeDate, SettingsKey.mapRelativeDate); + await migrator.migrateBool(StoreKey.legacyMapIncludeArchived, SettingsKey.mapIncludeArchived); + await migrator.migrateEnumIndex(StoreKey.legacyMapThemeMode, SettingsKey.mapThemeMode, ThemeMode.values); + await migrator.migrateBool(StoreKey.legacyMapwithPartners, SettingsKey.mapWithPartners); + // Timeline + await migrator.migrateInt(StoreKey.legacyTilesPerRow, SettingsKey.timelineTilesPerRow); + await migrator.migrateEnumIndex( + StoreKey.legacyGroupAssetsBy, + SettingsKey.timelineGroupAssetsBy, + GroupAssetsBy.values, + ); + await migrator.migrateBool(StoreKey.legacyStorageIndicator, SettingsKey.timelineStorageIndicator); + // Image + await migrator.migrateBool(StoreKey.legacyPreferRemoteImage, SettingsKey.imagePreferRemote); + await migrator.migrateBool(StoreKey.legacyLoadOriginal, SettingsKey.imageLoadOriginal); + // Viewer + await migrator.migrateBool(StoreKey.legacyLoopVideo, SettingsKey.viewerLoopVideo); + await migrator.migrateBool(StoreKey.legacyLoadOriginalVideo, SettingsKey.viewerLoadOriginalVideo); + await migrator.migrateBool(StoreKey.legacyAutoPlayVideo, SettingsKey.viewerAutoPlayVideo); + await migrator.migrateBool(StoreKey.legacyTapToNavigate, SettingsKey.viewerTapToNavigate); + // Network + await migrator.migrateBool(StoreKey.legacyAutoEndpointSwitching, SettingsKey.networkAutoEndpointSwitching); + await migrator.migrateString(StoreKey.legacyPreferredWifiName, SettingsKey.networkPreferredWifiName); + await migrator.migrateString(StoreKey.legacyLocalEndpoint, SettingsKey.networkLocalEndpoint); + await _migrateExternalEndpointList(migrator); + await _migrateCustomHeaders(migrator); + // Album + await _migrateAlbumSortMode(migrator); + await migrator.migrateBool(StoreKey.legacySelectedAlbumSortReverse, SettingsKey.albumIsReverse); + await migrator.migrateBool(StoreKey.legacyAlbumGridView, SettingsKey.albumIsGrid); + // Backup + await migrator.migrateBool(StoreKey.legacyEnableBackup, SettingsKey.backupEnabled); + await migrator.migrateBool(StoreKey.legacyUseWifiForUploadVideos, SettingsKey.backupUseCellularForVideos); + await migrator.migrateBool(StoreKey.legacyUseWifiForUploadPhotos, SettingsKey.backupUseCellularForPhotos); + await migrator.migrateBool(StoreKey.legacyBackupRequireCharging, SettingsKey.backupRequireCharging); + await migrator.migrateInt(StoreKey.legacyBackupTriggerDelay, SettingsKey.backupTriggerDelay); + await migrator.migrateBool(StoreKey.legacySyncAlbums, SettingsKey.backupSyncAlbums); + await migrator.complete(); +} + +Future _migrateAlbumSortMode(_StoreMigrator migrator) async { + final raw = await migrator.readLegacyStoreInt(StoreKey.legacySelectedAlbumSortOrder.id); + final mode = AlbumSortMode.values.firstWhereOrNull((e) => raw != null && e.storeIndex == raw); + if (mode == null) { + return; + } + + migrator.stage(StoreKey.legacySelectedAlbumSortOrder, SettingsKey.albumSortMode, mode); +} + +Future _migrateExternalEndpointList(_StoreMigrator migrator) async { + final raw = await migrator.readLegacyStoreString(StoreKey.legacyExternalEndpointList.id); + if (raw == null) { + return; + } + + final urls = []; + try { + final decoded = jsonDecode(raw); + if (decoded is List) { + for (final entry in decoded) { + final url = AuxilaryEndpoint.fromJson(entry).url; + if (url.isNotEmpty) { + urls.add(url); + } + } + } + } on FormatException { + // ignore invalid entries + } + + migrator.stage(StoreKey.legacyExternalEndpointList, SettingsKey.networkExternalEndpointList, urls); +} + +Future _migrateCustomHeaders(_StoreMigrator migrator) async { + final raw = await migrator.readLegacyStoreString(StoreKey.legacyCustomHeaders.id); + if (raw == null) { + return; + } + + final headers = {}; + try { + final decoded = jsonDecode(raw); + if (decoded is Map) { + decoded.forEach((key, value) { + if (key is String && value is String) { + headers[key] = value; + } + }); + } + } on FormatException { + // ignore invalid entries + } + + migrator.stage(StoreKey.legacyCustomHeaders, SettingsKey.networkCustomHeaders, headers); +} + +class _StoreMigrator { + final Drift _db; + final Map, Object> _cache = {}; + final List _migratedStoreIds = []; + + _StoreMigrator(this._db); + + Future migrateEnumIndex(StoreKey legacyKey, SettingsKey newKey, List values) async { + final index = await readLegacyStoreInt(legacyKey.id); + if (index == null) { + return; + } + + final enumValue = values.elementAtOrNull(index); + if (enumValue == null) { + return; + } + + _cache[newKey] = enumValue; + _migratedStoreIds.add(legacyKey.id); + } + + Future migrateEnumName( + StoreKey legacyKey, + SettingsKey newKey, + List values, + ) async { + final name = await readLegacyStoreString(legacyKey.id); + if (name == null) { + return; + } + + final enumValue = values.firstWhereOrNull((e) => e.name == name); + if (enumValue == null) { + return; + } + + _cache[newKey] = enumValue; + _migratedStoreIds.add(legacyKey.id); + } + + Future migrateBool(StoreKey legacyKey, SettingsKey newKey) async { + final intValue = await readLegacyStoreInt(legacyKey.id); + if (intValue == null) { + return; + } + + final boolValue = intValue != 0; + _cache[newKey] = boolValue; + _migratedStoreIds.add(legacyKey.id); + } + + Future migrateInt(StoreKey legacyKey, SettingsKey newKey) async { + final intValue = await readLegacyStoreInt(legacyKey.id); + if (intValue == null) { + return; + } + + _cache[newKey] = intValue; + _migratedStoreIds.add(legacyKey.id); + } + + Future migrateString(StoreKey legacyKey, SettingsKey newKey) async { + final value = await readLegacyStoreString(legacyKey.id); + if (value == null) { + return; + } + + _cache[newKey] = value; + _migratedStoreIds.add(legacyKey.id); + } + + void stage(StoreKey legacyKey, SettingsKey newKey, T value) { + _cache[newKey] = value; + _migratedStoreIds.add(legacyKey.id); + } + + Future complete() async { + await _db.batch((batch) { + for (final entry in _cache.entries) { + if (entry.value == defaultConfig.read(entry.key)) { + continue; + } + batch.insert( + _db.settingsEntity, + SettingsEntityCompanion(key: Value(entry.key.name), value: Value(entry.key.encode(entry.value))), + mode: InsertMode.insertOrReplace, + ); + } + }); + await deleteLegacyStoreRows(_migratedStoreIds); + } + + Future readLegacyStoreString(int id) async { + final row = await (_db.storeEntity.select()..where((t) => t.id.equals(id))).getSingleOrNull(); + return row?.stringValue; + } + + Future readLegacyStoreInt(int id) async { + final row = await (_db.storeEntity.select()..where((t) => t.id.equals(id))).getSingleOrNull(); + return row?.intValue; + } + + Future deleteLegacyStoreRows(List ids) async { + if (ids.isEmpty) { + return; + } + await (_db.storeEntity.delete()..where((t) => t.id.isIn(ids))).go(); + } +} diff --git a/mobile/lib/utils/openapi_patching.dart b/mobile/lib/utils/openapi_patching.dart index 38c805a42e..eca190ce8b 100644 --- a/mobile/lib/utils/openapi_patching.dart +++ b/mobile/lib/utils/openapi_patching.dart @@ -1,67 +1,58 @@ +import 'package:flutter/foundation.dart'; import 'package:openapi/api.dart'; -dynamic upgradeDto(dynamic value, String targetType) { - switch (targetType) { - case 'UserPreferencesResponseDto': - if (value is Map) { - addDefault(value, 'download.includeEmbeddedVideos', false); - addDefault(value, 'folders', FoldersResponse(enabled: false, sidebarWeb: false).toJson()); - addDefault(value, 'memories', MemoriesResponse(enabled: true, duration: 5).toJson()); - addDefault(value, 'ratings', RatingsResponse(enabled: false).toJson()); - addDefault(value, 'people', PeopleResponse(enabled: true, sidebarWeb: false).toJson()); - addDefault(value, 'tags', TagsResponse(enabled: false, sidebarWeb: false).toJson()); - addDefault(value, 'sharedLinks', SharedLinksResponse(enabled: true, sidebarWeb: false).toJson()); - addDefault(value, 'cast', CastResponse(gCastEnabled: false).toJson()); - addDefault(value, 'albums', {'defaultAssetOrder': 'desc'}); - } - break; - case 'ServerConfigDto': - if (value is Map) { - addDefault(value, 'mapLightStyleUrl', 'https://tiles.immich.cloud/v1/style/light.json'); - addDefault(value, 'mapDarkStyleUrl', 'https://tiles.immich.cloud/v1/style/dark.json'); - } - case 'UserResponseDto': - if (value is Map) { - addDefault(value, 'profileChangedAt', DateTime.now().toIso8601String()); - } - break; - case 'AssetResponseDto': - if (value is Map) { - addDefault(value, 'visibility', 'timeline'); - addDefault(value, 'createdAt', DateTime.now().toIso8601String()); - addDefault(value, 'isEdited', false); - } - break; - case 'UserAdminResponseDto': - if (value is Map) { - addDefault(value, 'profileChangedAt', DateTime.now().toIso8601String()); - } - break; - case 'LoginResponseDto': - if (value is Map) { - addDefault(value, 'isOnboarded', false); - } - break; - case 'SyncUserV1': - if (value is Map) { - addDefault(value, 'profileChangedAt', DateTime.now().toIso8601String()); - addDefault(value, 'hasProfileImage', false); - } - case 'SyncAssetV1': - if (value is Map) { - addDefault(value, 'isEdited', false); - } - case 'ServerFeaturesDto': - if (value is Map) { - addDefault(value, 'ocr', false); - } - break; - case 'MemoriesResponse': - if (value is Map) { - addDefault(value, 'duration', 5); - } - break; +abstract interface class _Dynamic { + Object? resolve(); +} + +class _CurrentTimestamp implements _Dynamic { + const _CurrentTimestamp(); + + @override + Object? resolve() => DateTime.now().toIso8601String(); +} + +const _now = _CurrentTimestamp(); + +@visibleForTesting +final Map> openApiPatches = { + 'UserPreferencesResponseDto': { + 'download.includeEmbeddedVideos': false, + 'folders': FoldersResponse(enabled: false, sidebarWeb: false).toJson(), + 'memories': MemoriesResponse(enabled: true, duration: 5).toJson(), + 'ratings': RatingsResponse(enabled: false).toJson(), + 'people': PeopleResponse(enabled: true, sidebarWeb: false).toJson(), + 'tags': TagsResponse(enabled: false, sidebarWeb: false).toJson(), + 'sharedLinks': SharedLinksResponse(enabled: true, sidebarWeb: false).toJson(), + 'cast': CastResponse(gCastEnabled: false).toJson(), + 'albums': {'defaultAssetOrder': 'desc'}, + }, + 'ServerConfigDto': { + 'mapLightStyleUrl': 'https://tiles.immich.cloud/v1/style/light.json', + 'mapDarkStyleUrl': 'https://tiles.immich.cloud/v1/style/dark.json', + 'minFaces': 3, + }, + 'UserResponseDto': {'profileChangedAt': _now}, + 'AssetResponseDto': {'visibility': 'timeline', 'createdAt': _now, 'isEdited': false}, + 'UserAdminResponseDto': {'profileChangedAt': _now}, + 'LoginResponseDto': {'isOnboarded': false}, + 'SyncUserV1': {'profileChangedAt': _now, 'hasProfileImage': false}, + 'SyncAssetV1': {'isEdited': false}, + 'ServerFeaturesDto': {'ocr': false, 'realtimeTranscoding': false}, + 'MemoriesResponse': {'duration': 5}, +}; + +void upgradeDto(dynamic value, String targetType) { + if (value is! Map) { + return; } + final fields = openApiPatches[targetType]; + if (fields == null) { + return; + } + fields.forEach((key, defaultValue) { + addDefault(value, key, defaultValue is _Dynamic ? defaultValue.resolve() : defaultValue); + }); } addDefault(dynamic value, String keys, dynamic defaultValue) { diff --git a/mobile/lib/utils/semver.dart b/mobile/lib/utils/semver.dart index aebfd2fe4c..8080af00b0 100644 --- a/mobile/lib/utils/semver.dart +++ b/mobile/lib/utils/semver.dart @@ -1,36 +1,42 @@ -enum SemVerType { major, minor, patch } +enum SemVerType { major, minor, patch, prerelease } class SemVer { final int major; final int minor; final int patch; + final int? prerelease; - const SemVer({required this.major, required this.minor, required this.patch}); + const SemVer({required this.major, required this.minor, required this.patch, this.prerelease}); @override String toString() { - return '$major.$minor.$patch'; + return '$major.$minor.$patch${prerelease == null ? '' : '-rc.$prerelease'}'; } - SemVer copyWith({int? major, int? minor, int? patch}) { - return SemVer(major: major ?? this.major, minor: minor ?? this.minor, patch: patch ?? this.patch); + SemVer copyWith({int? major, int? minor, int? patch, int? prerelease}) { + return SemVer( + major: major ?? this.major, + minor: minor ?? this.minor, + patch: patch ?? this.patch, + prerelease: prerelease ?? this.prerelease, + ); } + static final _pattern = RegExp(r'^v?(\d+)\.(\d+)\.(\d+)(?:-rc\.(\d+))?(?:[-+].*)?$', caseSensitive: false); + factory SemVer.fromString(String version) { - if (version.toLowerCase().startsWith("v")) { - version = version.substring(1); - } - - final parts = version.split("-")[0].split('.'); - if (parts.length != 3) { + final match = _pattern.firstMatch(version); + if (match == null) { throw FormatException('Invalid semantic version string: $version'); } - try { - return SemVer(major: int.parse(parts[0]), minor: int.parse(parts[1]), patch: int.parse(parts[2])); - } catch (e) { - throw FormatException('Invalid semantic version string: $version'); - } + final prerelease = match.group(4); + return SemVer( + major: int.parse(match.group(1)!), + minor: int.parse(match.group(2)!), + patch: int.parse(match.group(3)!), + prerelease: prerelease == null ? null : int.parse(prerelease), + ); } bool operator >(SemVer other) { @@ -40,7 +46,10 @@ class SemVer { if (minor != other.minor) { return minor > other.minor; } - return patch > other.patch; + if (patch != other.patch) { + return patch > other.patch; + } + return _comparePrerelease(other) > 0; } bool operator <(SemVer other) { @@ -50,7 +59,23 @@ class SemVer { if (minor != other.minor) { return minor < other.minor; } - return patch < other.patch; + if (patch != other.patch) { + return patch < other.patch; + } + return _comparePrerelease(other) < 0; + } + + int _comparePrerelease(SemVer other) { + if (prerelease == other.prerelease) { + return 0; + } + if (prerelease == null) { + return 1; + } + if (other.prerelease == null) { + return -1; + } + return prerelease!.compareTo(other.prerelease!); } bool operator >=(SemVer other) { @@ -63,9 +88,15 @@ class SemVer { @override bool operator ==(Object other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } - return other is SemVer && other.major == major && other.minor == minor && other.patch == patch; + return other is SemVer && + other.major == major && + other.minor == minor && + other.patch == patch && + other.prerelease == prerelease; } SemVerType? differenceType(SemVer other) { @@ -78,10 +109,13 @@ class SemVer { if (patch != other.patch) { return SemVerType.patch; } + if (prerelease != other.prerelease) { + return SemVerType.prerelease; + } return null; } @override - int get hashCode => major.hashCode ^ minor.hashCode ^ patch.hashCode; + int get hashCode => major.hashCode ^ minor.hashCode ^ patch.hashCode ^ prerelease.hashCode; } diff --git a/mobile/lib/utils/url_helper.dart b/mobile/lib/utils/url_helper.dart index e3d5b8ed57..b7dc41c4cf 100644 --- a/mobile/lib/utils/url_helper.dart +++ b/mobile/lib/utils/url_helper.dart @@ -24,6 +24,17 @@ String? getServerUrl() { ); } +String? buildSharedLinkUrl({required String? baseUrl, required String key, String? slug}) { + if (baseUrl == null || baseUrl.isEmpty) { + return null; + } + + final normalizedBaseUrl = baseUrl.endsWith('/') ? baseUrl : '$baseUrl/'; + final path = (slug != null && slug.isNotEmpty) ? 's/$slug' : 'share/$key'; + + return '$normalizedBaseUrl$path'; +} + /// Converts a Unicode URL to its ASCII-compatible encoding (Punycode). /// /// This is especially useful for internationalized domain names (IDNs), @@ -42,13 +53,17 @@ String? getServerUrl() { /// String punycodeEncodeUrl(String serverUrl) { final serverUri = Uri.tryParse(serverUrl); - if (serverUri == null || serverUri.host.isEmpty) return ''; + if (serverUri == null || serverUri.host.isEmpty) { + return ''; + } final encodedHost = Uri.decodeComponent(serverUri.host) .split('.') .map((segment) { // If segment is already ASCII, then return as it is. - if (segment.runes.every((c) => c < 0x80)) return segment; + if (segment.runes.every((c) => c < 0x80)) { + return segment; + } return 'xn--${punycodeEncode(segment)}'; }) .join('.'); @@ -75,7 +90,9 @@ String punycodeEncodeUrl(String serverUrl) { /// String? punycodeDecodeUrl(String? serverUrl) { final serverUri = serverUrl != null ? Uri.tryParse(serverUrl) : null; - if (serverUri == null || serverUri.host.isEmpty) return null; + if (serverUri == null || serverUri.host.isEmpty) { + return null; + } final decodedHost = serverUri.host .split('.') diff --git a/mobile/lib/widgets/activities/comment_bubble.dart b/mobile/lib/widgets/activities/comment_bubble.dart index 22cb0586bc..95cff7b87d 100644 --- a/mobile/lib/widgets/activities/comment_bubble.dart +++ b/mobile/lib/widgets/activities/comment_bubble.dart @@ -35,7 +35,9 @@ class CommentBubble extends ConsumerWidget { Future openAssetViewer() async { final activityService = ref.read(activityServiceProvider); final route = await activityService.buildAssetViewerRoute(activity.assetId!, ref); - if (route != null) await context.pushRoute(route); + if (route != null) { + await context.pushRoute(route); + } } // avatar (hidden for own messages) diff --git a/mobile/lib/widgets/asset_viewer/video_controls.dart b/mobile/lib/widgets/asset_viewer/video_controls.dart index 89b0f0ec30..0f1e0e020d 100644 --- a/mobile/lib/widgets/asset_viewer/video_controls.dart +++ b/mobile/lib/widgets/asset_viewer/video_controls.dart @@ -49,7 +49,9 @@ class _VideoControlsState extends ConsumerState { } void _onHideTimer() { - if (!mounted) return; + if (!mounted) { + return; + } if (ref.read(_provider).status == VideoPlaybackStatus.playing) { ref.read(assetViewerProvider.notifier).setControls(false); } @@ -91,7 +93,9 @@ class _VideoControlsState extends ConsumerState { final isFinished = !isCasting && videoStatus == VideoPlaybackStatus.completed; ref.listen(assetViewerProvider.select((v) => v.showingControls), (prev, showing) { - if (showing && prev != showing) _hideTimer.reset(); + if (showing && prev != showing) { + _hideTimer.reset(); + } }); ref.listen(_provider.select((v) => v.status), (_, __) => _hideTimer.reset()); @@ -119,13 +123,15 @@ class _VideoControlsState extends ConsumerState { onPressed: () => _toggle(isCasting), ), const Spacer(), - Text( - "${position.format()} / ${duration.format()}", - style: const TextStyle( - color: Colors.white, - fontWeight: FontWeight.w500, - fontFeatures: [FontFeature.tabularFigures()], - shadows: VideoControls._controlShadows, + IgnorePointer( + child: Text( + "${position.format()} / ${duration.format()}", + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.w500, + fontFeatures: [FontFeature.tabularFigures()], + shadows: VideoControls._controlShadows, + ), ), ), const SizedBox(width: 12), diff --git a/mobile/lib/widgets/common/app_bar_dialog/app_bar_server_info.dart b/mobile/lib/widgets/common/app_bar_dialog/app_bar_server_info.dart index 2809505c58..a209d280c3 100644 --- a/mobile/lib/widgets/common/app_bar_dialog/app_bar_server_info.dart +++ b/mobile/lib/widgets/common/app_bar_dialog/app_bar_server_info.dart @@ -50,9 +50,7 @@ class AppBarServerInfo extends HookConsumerWidget { divider, _ServerInfoItem( label: "server_version".tr(), - text: serverInfoState.serverVersion.major > 0 - ? "${serverInfoState.serverVersion.major}.${serverInfoState.serverVersion.minor}.${serverInfoState.serverVersion.patch}" - : "--", + text: serverInfoState.serverVersion.major > 0 ? "${serverInfoState.serverVersion}" : "--", ), divider, _ServerInfoItem(label: "server_info_box_server_url".tr(), text: getServerUrl() ?? '--', tooltip: true), @@ -60,9 +58,7 @@ class AppBarServerInfo extends HookConsumerWidget { divider, _ServerInfoItem( label: "latest_version".tr(), - text: serverInfoState.latestVersion!.major > 0 - ? "${serverInfoState.latestVersion!.major}.${serverInfoState.latestVersion!.minor}.${serverInfoState.latestVersion!.patch}" - : "--", + text: serverInfoState.latestVersion!.major > 0 ? "${serverInfoState.latestVersion!}" : "--", tooltip: true, icon: serverInfoState.versionStatus == VersionStatus.serverOutOfDate ? const Icon(Icons.info, color: Color.fromARGB(255, 243, 188, 106), size: 12) diff --git a/mobile/lib/widgets/common/date_time_picker.dart b/mobile/lib/widgets/common/date_time_picker.dart index 9cc8de29ee..0ebd7bba93 100644 --- a/mobile/lib/widgets/common/date_time_picker.dart +++ b/mobile/lib/widgets/common/date_time_picker.dart @@ -190,7 +190,9 @@ class _TimeZoneOffset implements Comparable<_TimeZoneOffset> { @override bool operator ==(Object other) { - if (identical(this, other)) return true; + if (identical(this, other)) { + return true; + } return other is _TimeZoneOffset && other.display == display && other.offsetInMilliseconds == offsetInMilliseconds; } diff --git a/mobile/lib/widgets/common/immich_sliver_app_bar.dart b/mobile/lib/widgets/common/immich_sliver_app_bar.dart index cb429c9f48..a81082753a 100644 --- a/mobile/lib/widgets/common/immich_sliver_app_bar.dart +++ b/mobile/lib/widgets/common/immich_sliver_app_bar.dart @@ -6,13 +6,12 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/setting.model.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/models/server_info/server_info.model.dart'; import 'package:immich_mobile/providers/backup/drift_backup.provider.dart'; import 'package:immich_mobile/providers/cast.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/setting.provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; import 'package:immich_mobile/providers/sync_status.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; @@ -193,64 +192,51 @@ class _BackupIndicator extends ConsumerWidget { } Widget? _getBackupBadgeIcon(BuildContext context, WidgetRef ref) { - final backupStateStream = ref.watch(settingsProvider).watch(Setting.enableBackup); + final backupEnabled = ref.watch(appConfigProvider.select((c) => c.backup.enabled)); final hasError = ref.watch(driftBackupProvider.select((state) => state.error != BackupError.none)); final isDarkTheme = context.isDarkTheme; final iconColor = isDarkTheme ? Colors.white : Colors.black; final isUploading = ref.watch(driftBackupProvider.select((state) => state.uploadItems.isNotEmpty)); - return StreamBuilder( - stream: backupStateStream, - initialData: false, - builder: (ctx, snapshot) { - final backupEnabled = snapshot.data ?? false; + if (!backupEnabled) { + return _BadgeLabel( + Icon(Icons.cloud_off_rounded, size: 9, color: iconColor, semanticLabel: 'backup_controller_page_backup'.tr()), + ); + } - if (!backupEnabled) { - return _BadgeLabel( - Icon( - Icons.cloud_off_rounded, - size: 9, - color: iconColor, - semanticLabel: 'backup_controller_page_backup'.tr(), + if (hasError) { + return _BadgeLabel( + Icon( + Icons.warning_rounded, + size: 12, + color: context.colorScheme.error, + semanticLabel: 'backup_controller_page_backup'.tr(), + ), + backgroundColor: context.colorScheme.errorContainer, + ); + } + + if (isUploading) { + return _BadgeLabel( + Container( + padding: const EdgeInsets.all(3.5), + child: Theme( + data: context.themeData.copyWith( + progressIndicatorTheme: context.themeData.progressIndicatorTheme.copyWith(year2023: true), ), - ); - } - - if (hasError) { - return _BadgeLabel( - Icon( - Icons.warning_rounded, - size: 12, - color: context.colorScheme.error, - semanticLabel: 'backup_controller_page_backup'.tr(), + child: CircularProgressIndicator( + strokeWidth: 2, + strokeCap: StrokeCap.round, + valueColor: AlwaysStoppedAnimation(iconColor), + semanticsLabel: 'backup_controller_page_backup'.tr(), ), - backgroundColor: context.colorScheme.errorContainer, - ); - } + ), + ), + ); + } - if (isUploading) { - return _BadgeLabel( - Container( - padding: const EdgeInsets.all(3.5), - child: Theme( - data: context.themeData.copyWith( - progressIndicatorTheme: context.themeData.progressIndicatorTheme.copyWith(year2023: true), - ), - child: CircularProgressIndicator( - strokeWidth: 2, - strokeCap: StrokeCap.round, - valueColor: AlwaysStoppedAnimation(iconColor), - semanticsLabel: 'backup_controller_page_backup'.tr(), - ), - ), - ), - ); - } - - return _BadgeLabel( - Icon(Icons.check_outlined, size: 9, color: iconColor, semanticLabel: 'backup_controller_page_backup'.tr()), - ); - }, + return _BadgeLabel( + Icon(Icons.check_outlined, size: 9, color: iconColor, semanticLabel: 'backup_controller_page_backup'.tr()), ); } } diff --git a/mobile/lib/widgets/common/remote_album_sliver_app_bar.dart b/mobile/lib/widgets/common/remote_album_sliver_app_bar.dart index 50746f5cbd..2fc136302d 100644 --- a/mobile/lib/widgets/common/remote_album_sliver_app_bar.dart +++ b/mobile/lib/widgets/common/remote_album_sliver_app_bar.dart @@ -18,6 +18,7 @@ import 'package:immich_mobile/providers/infrastructure/current_album.provider.da import 'package:immich_mobile/providers/infrastructure/remote_album.provider.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; +import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/widgets/album/remote_album_shared_user_icons.dart'; class RemoteAlbumSliverAppBar extends ConsumerStatefulWidget { @@ -89,6 +90,10 @@ class _MesmerizingSliverAppBarState extends ConsumerState context.maybePop(), ), actions: [ + IconButton( + onPressed: () => context.pushRoute(DriftSlideshowRoute(timeline: ref.read(timelineServiceProvider))), + icon: Icon(Icons.slideshow_outlined, color: actionIconColor, shadows: actionIconShadows), + ), if (currentAlbum.isActivityEnabled && currentAlbum.isShared) IconButton( icon: Icon(Icons.chat_outlined, color: actionIconColor, shadows: actionIconShadows), diff --git a/mobile/lib/widgets/common/tag_picker.dart b/mobile/lib/widgets/common/tag_picker.dart index 0ab25d14cb..0265cf7e6c 100644 --- a/mobile/lib/widgets/common/tag_picker.dart +++ b/mobile/lib/widgets/common/tag_picker.dart @@ -8,12 +8,78 @@ import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/providers/infrastructure/tag.provider.dart'; import 'package:immich_mobile/widgets/common/search_field.dart'; -class TagPicker extends HookConsumerWidget { - const TagPicker({super.key, required this.onSelect, required this.filter}); +String _trimSlashes(String s) => s.replaceAll(RegExp(r'^/+|/+$'), ''); + +Future<(Set, Set)?> showTagPickerModal({required BuildContext context, Set? initialSelection}) { + return showDialog<(Set, Set)?>( + context: context, + builder: (context) => _TagPickerModal(initialSelection: initialSelection), + ); +} + +class _TagPickerModal extends HookConsumerWidget { + final Set? initialSelection; + + const _TagPickerModal({this.initialSelection}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final selectedTagIds = useState>(initialSelection ?? {}); + final newTagValues = useState>({}); + + void onSelectExistingTag(Iterable tags) { + selectedTagIds.value = tags.map((tag) => tag.id).toSet(); + } + + void onSelectNewTag(Set tags) { + newTagValues.value = tags; + } + + return AlertDialog( + contentPadding: const EdgeInsets.symmetric(vertical: 16, horizontal: 0), + actions: [ + TextButton( + onPressed: () => context.pop(), + child: Text( + "cancel", + style: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w600, + color: context.colorScheme.error, + ), + ).tr(), + ), + TextButton( + onPressed: () => context.pop((selectedTagIds.value, newTagValues.value)), + child: Text( + "action_common_update", + style: context.textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.w600, color: context.primaryColor), + ).tr(), + ), + ], + content: SizedBox( + width: MediaQuery.of(context).size.width * 0.8, + height: MediaQuery.of(context).size.height * 0.6, + child: TagPicker( + onSelectExistingTag: onSelectExistingTag, + filter: selectedTagIds.value, + onSelectNewTag: onSelectNewTag, + ), + ), + ); + } +} + +class TagPicker extends HookConsumerWidget { + const TagPicker({super.key, required this.onSelectExistingTag, required this.filter, this.onSelectNewTag}); - final Function(Iterable) onSelect; final Set filter; + /// Callback when existing tags are selected/deselected. + final Function(Iterable) onSelectExistingTag; + + /// If not null, shows a tile to create a new tag with user's filter input. + final Function(Set)? onSelectNewTag; + @override Widget build(BuildContext context, WidgetRef ref) { final formFocus = useFocusNode(); @@ -21,6 +87,7 @@ class TagPicker extends HookConsumerWidget { final tags = ref.watch(tagProvider); final selectedTagIds = useState>(filter); final borderRadius = const BorderRadius.all(Radius.circular(10)); + final selectedNewTagValues = useState>({}); return Column( children: [ @@ -41,13 +108,53 @@ class TagPicker extends HookConsumerWidget { Expanded( child: tags.widgetWhen( onData: (tags) { + final trimmedQuery = _trimSlashes(searchQuery.value); final queryResult = tags - .where((t) => t.value.toLowerCase().contains(searchQuery.value.toLowerCase())) + .where((t) => t.value.toLowerCase().contains(trimmedQuery.toLowerCase())) .toList(); + final showCreateTile = + (onSelectNewTag != null) && + trimmedQuery.isNotEmpty && + !tags.any((t) => t.value.toLowerCase() == trimmedQuery.toLowerCase()); + final isCreateSelected = selectedNewTagValues.value.contains(trimmedQuery); return ListView.builder( - itemCount: queryResult.length, + itemCount: queryResult.length + (showCreateTile ? 1 : 0), padding: const EdgeInsets.all(8), itemBuilder: (context, index) { + if (showCreateTile && index == queryResult.length) { + // Create new tag tile + return Padding( + padding: const EdgeInsets.only(bottom: 2.0), + child: Container( + decoration: BoxDecoration( + color: isCreateSelected ? context.primaryColor : context.primaryColor.withAlpha(25), + borderRadius: const BorderRadius.all(Radius.circular(10)), + ), + child: ListTile( + title: Text( + trimmedQuery, + style: context.textTheme.bodyLarge?.copyWith( + color: isCreateSelected ? context.colorScheme.onPrimary : context.colorScheme.onSurface, + ), + ), + trailing: Icon( + Icons.add, + color: isCreateSelected ? context.colorScheme.onPrimary : context.colorScheme.onSurface, + ), + onTap: () { + final newSelectedNewTagValues = {...selectedNewTagValues.value}; + if (isCreateSelected) { + newSelectedNewTagValues.remove(trimmedQuery); + } else { + newSelectedNewTagValues.add(trimmedQuery); + } + selectedNewTagValues.value = newSelectedNewTagValues; + onSelectNewTag!.call(newSelectedNewTagValues); + }, + ), + ), + ); + } final tag = queryResult[index]; final isSelected = selectedTagIds.value.any((id) => id == tag.id); @@ -73,7 +180,7 @@ class TagPicker extends HookConsumerWidget { newSelected.add(tag.id); } selectedTagIds.value = newSelected; - onSelect(tags.where((t) => newSelected.contains(t.id))); + onSelectExistingTag(tags.where((t) => newSelected.contains(t.id))); }, ), ), diff --git a/mobile/lib/widgets/forms/login/login_form.dart b/mobile/lib/widgets/forms/login/login_form.dart index fb3b9c5977..7615218159 100644 --- a/mobile/lib/widgets/forms/login/login_form.dart +++ b/mobile/lib/widgets/forms/login/login_form.dart @@ -15,13 +15,15 @@ import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart'; import 'package:immich_mobile/providers/auth.provider.dart'; import 'package:immich_mobile/providers/background_sync.provider.dart'; import 'package:immich_mobile/providers/gallery_permission.provider.dart'; import 'package:immich_mobile/providers/oauth.provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; +import 'package:immich_mobile/providers/view_intent/view_intent_handler.provider.dart'; import 'package:immich_mobile/providers/websocket.provider.dart'; -import 'package:immich_mobile/repositories/local_files_manager.repository.dart'; +import 'package:immich_mobile/repositories/permission.repository.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/utils/provider_utils.dart'; import 'package:immich_mobile/utils/url_helper.dart'; @@ -40,7 +42,9 @@ class LoginForm extends HookConsumerWidget { final log = Logger('LoginForm'); String? _validateUrl(String? url) { - if (url == null || url.isEmpty) return null; + if (url == null || url.isEmpty) { + return null; + } final parsedUrl = Uri.tryParse(url); if (parsedUrl == null || !parsedUrl.isAbsolute || !parsedUrl.scheme.startsWith("http") || parsedUrl.host.isEmpty) { @@ -51,9 +55,15 @@ class LoginForm extends HookConsumerWidget { } String? _validateEmail(String? email) { - if (email == null || email == '') return null; - if (email.endsWith(' ')) return 'login_form_err_trailing_whitespace'.tr(); - if (email.startsWith(' ')) return 'login_form_err_leading_whitespace'.tr(); + if (email == null || email == '') { + return null; + } + if (email.endsWith(' ')) { + return 'login_form_err_trailing_whitespace'.tr(); + } + if (email.startsWith(' ')) { + return 'login_form_err_leading_whitespace'.tr(); + } if (email.contains(' ') || !email.contains('@')) { return 'login_form_err_invalid_email'.tr(); } @@ -173,18 +183,20 @@ class LoginForm extends HookConsumerWidget { Future handleSyncFlow() async { final backgroundManager = ref.read(backgroundSyncProvider); + final viewIntentHandler = ref.read(viewIntentHandlerProvider); await backgroundManager.syncLocal(full: true); await backgroundManager.syncRemote(); + await viewIntentHandler.flushDeferredViewIntent(); await backgroundManager.hashAssets(); - if (Store.get(StoreKey.syncAlbums, false)) { + if (SettingsRepository.instance.appConfig.backup.syncAlbums) { await backgroundManager.syncLinkedAlbum(); } } getManageMediaPermission() async { - final hasPermission = await ref.read(localFilesManagerRepositoryProvider).hasManageMediaPermission(); + final hasPermission = await ref.read(permissionRepositoryProvider).hasManageMediaPermission(); if (!hasPermission) { await showDialog( context: context, @@ -215,7 +227,7 @@ class LoginForm extends HookConsumerWidget { ), TextButton( onPressed: () { - ref.read(localFilesManagerRepositoryProvider).requestManageMediaPermission(); + unawaited(ref.read(permissionRepositoryProvider).requestManageMediaPermission()); Navigator.of(context).pop(); }, child: Text( @@ -250,7 +262,7 @@ class LoginForm extends HookConsumerWidget { } unawaited(handleSyncFlow()); ref.read(websocketProvider.notifier).connect(); - unawaited(context.replaceRoute(const TabShellRoute())); + unawaited(context.router.replaceAll([const TabShellRoute()])); return; } } catch (error) { @@ -337,7 +349,7 @@ class LoginForm extends HookConsumerWidget { await getManageMediaPermission(); } unawaited(handleSyncFlow()); - unawaited(context.replaceRoute(const TabShellRoute())); + unawaited(context.router.replaceAll([const TabShellRoute()])); return; } } catch (error, stack) { @@ -389,19 +401,16 @@ class LoginForm extends HookConsumerWidget { mainAxisSize: MainAxisSize.max, children: [ ImmichForm( + onSubmit: getServerAuthSettings, submitText: 'next'.t(context: context), submitIcon: Icons.arrow_forward_rounded, - onSubmit: getServerAuthSettings, - child: ImmichTextInput( + builder: (_, form) => ImmichURLInput( controller: serverEndpointController, label: 'login_form_endpoint_url'.t(context: context), hintText: 'login_form_endpoint_hint'.t(context: context), validator: _validateUrl, - keyboardAction: TextInputAction.next, - keyboardType: TextInputType.url, - autofillHints: const [AutofillHints.url], - autoCorrect: false, - onSubmit: (ctx, _) => ImmichForm.of(ctx).submit(), + keyboardAction: .next, + onSubmit: (_) => form.submit(), ), ), ImmichTextButton( @@ -429,10 +438,10 @@ class LoginForm extends HookConsumerWidget { ), if (isPasswordLoginEnable.value) ImmichForm( + onSubmit: login, submitText: 'login'.t(context: context), submitIcon: Icons.login_rounded, - onSubmit: login, - child: Column( + builder: (context, form) => Column( spacing: ImmichSpacing.md, children: [ ImmichTextInput( @@ -443,7 +452,7 @@ class LoginForm extends HookConsumerWidget { keyboardAction: TextInputAction.next, keyboardType: TextInputType.emailAddress, autofillHints: const [AutofillHints.email], - onSubmit: (_, _) => passwordFocusNode.requestFocus(), + onSubmit: (_) => passwordFocusNode.requestFocus(), ), ImmichPasswordInput( controller: passwordController, @@ -451,17 +460,17 @@ class LoginForm extends HookConsumerWidget { label: 'password'.t(context: context), hintText: 'login_form_password_hint'.t(context: context), keyboardAction: TextInputAction.go, - onSubmit: (ctx, _) => ImmichForm.of(ctx).submit(), + onSubmit: (_) => form.submit(), ), ], ), ), if (isOauthEnable.value) ImmichForm( + onSubmit: oAuthLogin, submitText: oAuthButtonLabel.value, submitIcon: Icons.pin_outlined, - onSubmit: oAuthLogin, - child: isPasswordLoginEnable.value + builder: (context, _) => isPasswordLoginEnable.value ? Padding( padding: const EdgeInsets.only(left: 18.0, right: 18.0, top: 12.0), child: Divider(color: context.isDarkTheme ? Colors.white : Colors.black, height: 5), diff --git a/mobile/lib/widgets/photo_view/src/core/photo_view_core.dart b/mobile/lib/widgets/photo_view/src/core/photo_view_core.dart index 2f775f57e2..265feb756e 100644 --- a/mobile/lib/widgets/photo_view/src/core/photo_view_core.dart +++ b/mobile/lib/widgets/photo_view/src/core/photo_view_core.dart @@ -139,8 +139,6 @@ class PhotoViewCoreState extends State PhotoViewHeroAttributes? get heroAttributes => widget.heroAttributes; - late ScaleBoundaries cachedScaleBoundaries = widget.scaleBoundaries; - void handleScaleAnimation() { scale = _scaleAnimation!.value; } @@ -303,7 +301,7 @@ class PhotoViewCoreState extends State controller.scaleAnimationBuilder(_animateControllerScale); controller.rotationAnimationBuilder(_animateControllerRotation); - cachedScaleBoundaries = widget.scaleBoundaries; + _updateScaleBoundaries(); _scaleAnimationController = AnimationController(vsync: this) ..addListener(handleScaleAnimation) @@ -334,14 +332,29 @@ class PhotoViewCoreState extends State widget.onTapDown?.call(context, details, controller.value); } - @override - Widget build(BuildContext context) { - // Check if we need a recalc on the scale - if (widget.scaleBoundaries != cachedScaleBoundaries) { - markNeedsScaleRecalc = true; - cachedScaleBoundaries = widget.scaleBoundaries; + void _updateScaleBoundaries() { + final prev = controller.scaleBoundaries; + if (prev == widget.scaleBoundaries) { + return; } + if (prev != null && controller.scale != null && prev.initialScale > 0) { + final ratio = widget.scaleBoundaries.initialScale / prev.initialScale; + controller.setScaleInvisibly(controller.scale! * ratio); + } else { + markNeedsScaleRecalc = true; + } + controller.scaleBoundaries = widget.scaleBoundaries; + } + + @override + void didUpdateWidget(PhotoViewCore oldWidget) { + super.didUpdateWidget(oldWidget); + _updateScaleBoundaries(); + } + + @override + Widget build(BuildContext context) { return StreamBuilder( stream: controller.outputStateStream, initialData: controller.prevValue, diff --git a/mobile/lib/widgets/photo_view/src/photo_view_wrappers.dart b/mobile/lib/widgets/photo_view/src/photo_view_wrappers.dart index d8d2ae7ee5..a9cfeb3a40 100644 --- a/mobile/lib/widgets/photo_view/src/photo_view_wrappers.dart +++ b/mobile/lib/widgets/photo_view/src/photo_view_wrappers.dart @@ -145,7 +145,6 @@ class _ImageWrapperState extends State { _lastStack = null; _didLoadSynchronously = synchronousCall; - widget.controller.scaleBoundaries = scaleBoundaries; } synchronousCall && !_didLoadSynchronously ? setupCB() : setState(setupCB); diff --git a/mobile/lib/widgets/search/search_filter/star_rating_picker.dart b/mobile/lib/widgets/search/search_filter/star_rating_picker.dart index 5591b0e264..917d56e802 100644 --- a/mobile/lib/widgets/search/search_filter/star_rating_picker.dart +++ b/mobile/lib/widgets/search/search_filter/star_rating_picker.dart @@ -15,7 +15,9 @@ class StarRatingPicker extends HookWidget { return RadioGroup( groupValue: selectedRating.value?.rating, onChanged: (int? newValue) { - if (newValue == null) return; + if (newValue == null) { + return; + } final newFilter = SearchRatingFilter(rating: newValue); selectedRating.value = newFilter; onSelect(newFilter); diff --git a/mobile/lib/widgets/settings/advanced_settings.dart b/mobile/lib/widgets/settings/advanced_settings.dart index a38ccd3556..542a7cc5e2 100644 --- a/mobile/lib/widgets/settings/advanced_settings.dart +++ b/mobile/lib/widgets/settings/advanced_settings.dart @@ -7,9 +7,10 @@ import 'package:flutter_hooks/flutter_hooks.dart' hide Store; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/services/log.service.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; import 'package:immich_mobile/providers/infrastructure/platform.provider.dart'; import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart'; -import 'package:immich_mobile/repositories/local_files_manager.repository.dart'; +import 'package:immich_mobile/repositories/permission.repository.dart'; import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/utils/bytes_units.dart'; import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart'; @@ -30,8 +31,12 @@ class AdvancedSettings extends HookConsumerWidget { final manageLocalMediaAndroid = useAppSettingsState(AppSettingsEnum.manageLocalMediaAndroid); final isManageMediaSupported = useState(false); final manageMediaAndroidPermission = useState(false); - final levelId = useAppSettingsState(AppSettingsEnum.logLevel); - final preferRemote = useAppSettingsState(AppSettingsEnum.preferRemoteImage); + final levelId = useState(ref.read(appConfigProvider).logLevel.index); + final preferRemote = useState(ref.read(appConfigProvider).image.preferRemote); + useValueChanged( + preferRemote.value, + (_, __) => ref.read(settingsProvider).write(.imagePreferRemote, preferRemote.value), + ); final readonlyModeEnabled = useAppSettingsState(AppSettingsEnum.readonlyModeEnabled); final logLevel = Level.LEVELS[levelId.value].name; @@ -52,9 +57,7 @@ class AdvancedSettings extends HookConsumerWidget { () async { isManageMediaSupported.value = await checkAndroidVersion(); if (isManageMediaSupported.value) { - manageMediaAndroidPermission.value = await ref - .read(localFilesManagerRepositoryProvider) - .hasManageMediaPermission(); + manageMediaAndroidPermission.value = await ref.read(permissionRepositoryProvider).hasManageMediaPermission(); } }(); return null; @@ -77,7 +80,7 @@ class AdvancedSettings extends HookConsumerWidget { subtitle: "advanced_settings_sync_remote_deletions_subtitle".tr(), onChanged: (value) async { if (value) { - final result = await ref.read(localFilesManagerRepositoryProvider).requestManageMediaPermission(); + final result = await ref.read(permissionRepositoryProvider).requestManageMediaPermission(); manageLocalMediaAndroid.value = result; manageMediaAndroidPermission.value = result; } @@ -91,7 +94,7 @@ class AdvancedSettings extends HookConsumerWidget { ? const Color.fromARGB(255, 243, 188, 106) : null, onActionTap: () async { - final result = await ref.read(localFilesManagerRepositoryProvider).manageMediaPermission(); + final result = await ref.read(permissionRepositoryProvider).manageMediaPermission(); manageMediaAndroidPermission.value = result; }, ), diff --git a/mobile/lib/widgets/settings/asset_list_settings/asset_list_group_settings.dart b/mobile/lib/widgets/settings/asset_list_settings/asset_list_group_settings.dart index 42ea3acfc0..59adb335bb 100644 --- a/mobile/lib/widgets/settings/asset_list_settings/asset_list_group_settings.dart +++ b/mobile/lib/widgets/settings/asset_list_settings/asset_list_group_settings.dart @@ -1,12 +1,12 @@ import 'dart:async'; import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/timeline.model.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/providers/app_settings.provider.dart'; -import 'package:immich_mobile/services/app_settings.service.dart'; -import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart'; +import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; import 'package:immich_mobile/widgets/settings/setting_group_title.dart'; import 'package:immich_mobile/widgets/settings/settings_radio_list_tile.dart'; @@ -15,18 +15,17 @@ class GroupSettings extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final groupByIndex = useAppSettingsState(AppSettingsEnum.groupAssetsBy); - final groupBy = GroupAssetsBy.values[groupByIndex.value]; + final groupBy = useValueNotifier(ref.watch(appConfigProvider.select((s) => s.timeline.groupAssetsBy))); Future updateAppSettings(GroupAssetsBy groupBy) async { - await ref.watch(appSettingsServiceProvider).setSetting(AppSettingsEnum.groupAssetsBy, groupBy.index); + await ref.read(settingsProvider).write(.timelineGroupAssetsBy, groupBy); ref.invalidate(appSettingsServiceProvider); } void changeGroupValue(GroupAssetsBy? value) { if (value != null) { - groupByIndex.value = value.index; - unawaited(updateAppSettings(groupBy)); + groupBy.value = value; + unawaited(updateAppSettings(value)); } } @@ -52,7 +51,7 @@ class GroupSettings extends HookConsumerWidget { value: GroupAssetsBy.auto, ), ], - groupBy: groupBy, + groupBy: groupBy.value, onRadioChanged: changeGroupValue, ), ], diff --git a/mobile/lib/widgets/settings/asset_list_settings/asset_list_layout_settings.dart b/mobile/lib/widgets/settings/asset_list_settings/asset_list_layout_settings.dart index 55c8195947..f915df04f8 100644 --- a/mobile/lib/widgets/settings/asset_list_settings/asset_list_layout_settings.dart +++ b/mobile/lib/widgets/settings/asset_list_settings/asset_list_layout_settings.dart @@ -1,10 +1,10 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/providers/app_settings.provider.dart'; -import 'package:immich_mobile/services/app_settings.service.dart'; -import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart'; +import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; import 'package:immich_mobile/widgets/settings/setting_group_title.dart'; import 'package:immich_mobile/widgets/settings/settings_slider_list_tile.dart'; @@ -13,7 +13,10 @@ class LayoutSettings extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final tilesPerRow = useAppSettingsState(AppSettingsEnum.tilesPerRow); + final tilesPerRow = useState(ref.read(appConfigProvider.select((s) => s.timeline.tilesPerRow))); + useValueChanged(tilesPerRow.value, (_, __) { + ref.read(settingsProvider).write(.timelineTilesPerRow, tilesPerRow.value); + }); return Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -29,7 +32,9 @@ class LayoutSettings extends HookConsumerWidget { maxValue: 6, minValue: 2, noDivisons: 4, - onChangeEnd: (_) => ref.invalidate(appSettingsServiceProvider), + onChangeEnd: (value) { + ref.invalidate(appSettingsServiceProvider); + }, ), ], ); diff --git a/mobile/lib/widgets/settings/asset_list_settings/asset_list_settings.dart b/mobile/lib/widgets/settings/asset_list_settings/asset_list_settings.dart index 82394bdc07..3ac72d6612 100644 --- a/mobile/lib/widgets/settings/asset_list_settings/asset_list_settings.dart +++ b/mobile/lib/widgets/settings/asset_list_settings/asset_list_settings.dart @@ -1,10 +1,9 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/providers/app_settings.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/setting.provider.dart'; -import 'package:immich_mobile/services/app_settings.service.dart'; -import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart'; +import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; import 'package:immich_mobile/widgets/settings/asset_list_settings/asset_list_group_settings.dart'; import 'package:immich_mobile/widgets/settings/asset_list_settings/asset_list_layout_settings.dart'; import 'package:immich_mobile/widgets/settings/settings_sub_page_scaffold.dart'; @@ -15,13 +14,14 @@ class AssetListSettings extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final showStorageIndicator = useAppSettingsState(AppSettingsEnum.storageIndicator); + final storageIndicator = useValueNotifier(ref.watch(appConfigProvider.select((s) => s.timeline.storageIndicator))); final assetListSetting = [ SettingsSwitchListTile( - valueNotifier: showStorageIndicator, + valueNotifier: storageIndicator, title: 'theme_setting_asset_list_storage_indicator_title'.tr(), - onChanged: (_) { + onChanged: (value) { + ref.read(settingsProvider).write(.timelineStorageIndicator, value); ref.invalidate(appSettingsServiceProvider); ref.invalidate(settingsProvider); }, diff --git a/mobile/lib/widgets/settings/asset_viewer_settings/asset_viewer_settings.dart b/mobile/lib/widgets/settings/asset_viewer_settings/asset_viewer_settings.dart index a2bca2745f..f3b9039b2b 100644 --- a/mobile/lib/widgets/settings/asset_viewer_settings/asset_viewer_settings.dart +++ b/mobile/lib/widgets/settings/asset_viewer_settings/asset_viewer_settings.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:immich_mobile/widgets/settings/asset_viewer_settings/image_viewer_quality_setting.dart'; import 'package:immich_mobile/widgets/settings/asset_viewer_settings/image_viewer_tap_to_navigate_setting.dart'; import 'package:immich_mobile/widgets/settings/asset_viewer_settings/video_viewer_settings.dart'; +import 'package:immich_mobile/widgets/settings/asset_viewer_settings/slideshow_settings.dart'; import 'package:immich_mobile/widgets/settings/settings_sub_page_scaffold.dart'; class AssetViewerSettings extends StatelessWidget { @@ -13,6 +14,7 @@ class AssetViewerSettings extends StatelessWidget { const ImageViewerQualitySetting(), const ImageViewerTapToNavigateSetting(), const VideoViewerSettings(), + const SlideshowSettings(), ]; return SettingsSubPageScaffold(settings: assetViewerSetting, showDivider: true); diff --git a/mobile/lib/widgets/settings/asset_viewer_settings/image_viewer_quality_setting.dart b/mobile/lib/widgets/settings/asset_viewer_settings/image_viewer_quality_setting.dart index e437b82dd4..f65af6af9d 100644 --- a/mobile/lib/widgets/settings/asset_viewer_settings/image_viewer_quality_setting.dart +++ b/mobile/lib/widgets/settings/asset_viewer_settings/image_viewer_quality_setting.dart @@ -1,19 +1,21 @@ import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/providers/app_settings.provider.dart'; -import 'package:immich_mobile/services/app_settings.service.dart'; +import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; import 'package:immich_mobile/widgets/settings/setting_group_title.dart'; import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart'; -import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart'; class ImageViewerQualitySetting extends HookConsumerWidget { const ImageViewerQualitySetting({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { - final isPreview = useAppSettingsState(AppSettingsEnum.loadPreview); - final isOriginal = useAppSettingsState(AppSettingsEnum.loadOriginal); + final isOriginal = useState(ref.read(appConfigProvider).image.loadOriginal); + useValueChanged(isOriginal.value, (_, __) { + ref.read(settingsProvider).write(.imageLoadOriginal, isOriginal.value); + }); return Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -23,12 +25,6 @@ class ImageViewerQualitySetting extends HookConsumerWidget { icon: Icons.image_outlined, subtitle: "setting_image_viewer_help".t(context: context), ), - SettingsSwitchListTile( - valueNotifier: isPreview, - title: "setting_image_viewer_preview_title".t(context: context), - subtitle: "setting_image_viewer_preview_subtitle".t(context: context), - onChanged: (_) => ref.invalidate(appSettingsServiceProvider), - ), SettingsSwitchListTile( valueNotifier: isOriginal, title: "setting_image_viewer_original_title".t(context: context), diff --git a/mobile/lib/widgets/settings/asset_viewer_settings/image_viewer_tap_to_navigate_setting.dart b/mobile/lib/widgets/settings/asset_viewer_settings/image_viewer_tap_to_navigate_setting.dart index 759162cab8..730521e3c1 100644 --- a/mobile/lib/widgets/settings/asset_viewer_settings/image_viewer_tap_to_navigate_setting.dart +++ b/mobile/lib/widgets/settings/asset_viewer_settings/image_viewer_tap_to_navigate_setting.dart @@ -1,18 +1,20 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/providers/app_settings.provider.dart'; -import 'package:immich_mobile/services/app_settings.service.dart'; +import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; import 'package:immich_mobile/widgets/settings/settings_sub_title.dart'; import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart'; -import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart'; class ImageViewerTapToNavigateSetting extends HookConsumerWidget { const ImageViewerTapToNavigateSetting({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { - final tapToNavigate = useAppSettingsState(AppSettingsEnum.tapToNavigate); + final tapToNavigate = useState(ref.read(appConfigProvider).viewer.tapToNavigate); + useValueChanged(tapToNavigate.value, (_, __) { + ref.read(settingsProvider).write(.viewerTapToNavigate, tapToNavigate.value); + }); return Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -22,7 +24,6 @@ class ImageViewerTapToNavigateSetting extends HookConsumerWidget { valueNotifier: tapToNavigate, title: "setting_image_navigation_enable_title".tr(), subtitle: "setting_image_navigation_enable_subtitle".tr(), - onChanged: (_) => ref.invalidate(appSettingsServiceProvider), ), ], ); diff --git a/mobile/lib/widgets/settings/asset_viewer_settings/slideshow_settings.dart b/mobile/lib/widgets/settings/asset_viewer_settings/slideshow_settings.dart new file mode 100644 index 0000000000..5f93d429b0 --- /dev/null +++ b/mobile/lib/widgets/settings/asset_viewer_settings/slideshow_settings.dart @@ -0,0 +1,123 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; +import 'package:immich_mobile/widgets/settings/setting_group_title.dart'; +import 'package:immich_mobile/widgets/settings/settings_radio_list_tile.dart'; +import 'package:immich_mobile/widgets/settings/settings_slider_list_tile.dart'; +import 'package:immich_mobile/widgets/settings/settings_sub_title.dart'; +import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart'; + +class SlideshowSettings extends HookConsumerWidget { + const SlideshowSettings({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final slideshow = ref.read(appConfigProvider).slideshow; + final useTransition = useState(slideshow.transition); + final useRepeat = useState(slideshow.repeat); + final useDuration = useState(slideshow.duration); + final useLook = useState(slideshow.look); + final useDirection = useState(slideshow.direction); + + useValueChanged(useTransition.value, (_, __) { + ref.read(settingsProvider).write(.slideshowTransition, useTransition.value); + }); + useValueChanged(useRepeat.value, (_, __) { + ref.read(settingsProvider).write(.slideshowRepeat, useRepeat.value); + }); + useValueChanged(useDuration.value, (_, __) { + ref.read(settingsProvider).write(.slideshowDuration, useDuration.value); + }); + useValueChanged(useLook.value, (_, __) { + ref.read(settingsProvider).write(.slideshowLook, useLook.value); + }); + useValueChanged(useDirection.value, (_, __) { + ref.read(settingsProvider).write(.slideshowDirection, useDirection.value); + }); + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SettingGroupTitle( + title: 'slideshow'.t(context: context), + icon: Icons.slideshow_outlined, + ), + SettingsSwitchListTile( + valueNotifier: useTransition, + title: "show_slideshow_transition".t(context: context), + enabled: useDirection.value != SlideshowDirection.shuffle, + ), + SettingsSwitchListTile( + valueNotifier: useRepeat, + title: "slideshow_repeat".t(context: context), + subtitle: "slideshow_repeat_description".t(context: context), + ), + SettingsSliderListTile( + valueNotifier: useDuration, + text: "duration".t(context: context), + minValue: 5, + noDivisons: 5, + maxValue: 30, + ), + Padding( + padding: const EdgeInsets.only(top: 20), + child: SettingsSubTitle(title: 'look'.t(context: context)), + ), + SettingsRadioListTile( + groups: [ + SettingsRadioGroup( + title: 'contain'.t(context: context), + value: SlideshowLook.contain, + ), + SettingsRadioGroup( + title: 'cover'.t(context: context), + value: SlideshowLook.cover, + ), + SettingsRadioGroup( + title: 'blurred_background'.t(context: context), + value: SlideshowLook.blurredBackground, + ), + ], + groupBy: useLook.value, + onRadioChanged: (value) { + if (value != null) { + useLook.value = value; + } + }, + ), + Padding( + padding: const EdgeInsets.only(top: 20), + child: SettingsSubTitle(title: 'direction'.t(context: context)), + ), + Padding( + padding: const EdgeInsets.only(bottom: 32), + child: SettingsRadioListTile( + groups: [ + SettingsRadioGroup( + title: 'forward'.t(context: context), + value: SlideshowDirection.forward, + ), + SettingsRadioGroup( + title: 'backward'.t(context: context), + value: SlideshowDirection.backward, + ), + SettingsRadioGroup( + title: 'shuffle'.t(context: context), + value: SlideshowDirection.shuffle, + ), + ], + groupBy: useDirection.value, + onRadioChanged: (value) { + if (value != null) { + useDirection.value = value; + } + }, + ), + ), + ], + ); + } +} diff --git a/mobile/lib/widgets/settings/asset_viewer_settings/video_viewer_settings.dart b/mobile/lib/widgets/settings/asset_viewer_settings/video_viewer_settings.dart index c03dcc51b4..81929d95b9 100644 --- a/mobile/lib/widgets/settings/asset_viewer_settings/video_viewer_settings.dart +++ b/mobile/lib/widgets/settings/asset_viewer_settings/video_viewer_settings.dart @@ -1,20 +1,30 @@ import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; -import 'package:immich_mobile/providers/app_settings.provider.dart'; -import 'package:immich_mobile/services/app_settings.service.dart'; +import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; import 'package:immich_mobile/widgets/settings/setting_group_title.dart'; import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart'; -import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart'; class VideoViewerSettings extends HookConsumerWidget { const VideoViewerSettings({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { - final useLoopVideo = useAppSettingsState(AppSettingsEnum.loopVideo); - final useOriginalVideo = useAppSettingsState(AppSettingsEnum.loadOriginalVideo); - final useAutoPlayVideo = useAppSettingsState(AppSettingsEnum.autoPlayVideo); + final viewer = ref.read(appConfigProvider).viewer; + final useAutoPlayVideo = useState(viewer.autoPlayVideo); + final useLoopVideo = useState(viewer.loopVideo); + final useOriginalVideo = useState(viewer.loadOriginalVideo); + + useValueChanged(useAutoPlayVideo.value, (_, __) { + ref.read(settingsProvider).write(.viewerAutoPlayVideo, useAutoPlayVideo.value); + }); + useValueChanged(useLoopVideo.value, (_, __) { + ref.read(settingsProvider).write(.viewerLoopVideo, useLoopVideo.value); + }); + useValueChanged(useOriginalVideo.value, (_, __) { + ref.read(settingsProvider).write(.viewerLoadOriginalVideo, useOriginalVideo.value); + }); return Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -27,19 +37,16 @@ class VideoViewerSettings extends HookConsumerWidget { valueNotifier: useAutoPlayVideo, title: "setting_video_viewer_auto_play_title".t(context: context), subtitle: "setting_video_viewer_auto_play_subtitle".t(context: context), - onChanged: (_) => ref.invalidate(appSettingsServiceProvider), ), SettingsSwitchListTile( valueNotifier: useLoopVideo, title: "setting_video_viewer_looping_title".t(context: context), subtitle: "loop_videos_description".t(context: context), - onChanged: (_) => ref.invalidate(appSettingsServiceProvider), ), SettingsSwitchListTile( valueNotifier: useOriginalVideo, title: "setting_video_viewer_original_video_title".t(context: context), subtitle: "setting_video_viewer_original_video_subtitle".t(context: context), - onChanged: (_) => ref.invalidate(appSettingsServiceProvider), ), ], ); diff --git a/mobile/lib/widgets/settings/backup_settings/drift_backup_settings.dart b/mobile/lib/widgets/settings/backup_settings/drift_backup_settings.dart index 2c179c42ea..e5debb43fe 100644 --- a/mobile/lib/widgets/settings/backup_settings/drift_backup_settings.dart +++ b/mobile/lib/widgets/settings/backup_settings/drift_backup_settings.dart @@ -4,18 +4,17 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/album/local_album.model.dart'; -import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/domain/models/config/app_config.dart'; +import 'package:immich_mobile/domain/models/settings_key.dart'; import 'package:immich_mobile/domain/services/sync_linked_album.service.dart'; -import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/platform_extensions.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; -import 'package:immich_mobile/providers/app_settings.provider.dart'; import 'package:immich_mobile/providers/background_sync.provider.dart'; import 'package:immich_mobile/providers/backup/backup_album.provider.dart'; import 'package:immich_mobile/providers/infrastructure/platform.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; -import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/widgets/settings/setting_group_title.dart'; import 'package:immich_mobile/widgets/settings/setting_list_tile.dart'; import 'package:immich_mobile/widgets/settings/settings_sub_page_scaffold.dart'; @@ -31,8 +30,8 @@ class DriftBackupSettings extends ConsumerWidget { title: "network_requirements".t(context: context), icon: Icons.cell_tower, ), - const _UseWifiForUploadVideosButton(), - const _UseWifiForUploadPhotosButton(), + const _UseCellularForVideosButton(), + const _UseCellularForPhotosButton(), if (CurrentPlatform.isAndroid) ...[ const Divider(), SettingGroupTitle( @@ -74,6 +73,9 @@ class _AlbumSyncActionButtonState extends ConsumerState<_AlbumSyncActionButton> } catch (_) { } finally { Future.delayed(const Duration(seconds: 1), () { + if (!mounted) { + return; + } setState(() { isAlbumSyncInProgress = false; }); @@ -96,64 +98,58 @@ class _AlbumSyncActionButtonState extends ConsumerState<_AlbumSyncActionButton> @override Widget build(BuildContext context) { + final albumSyncEnable = ref.watch(appConfigProvider.select((c) => c.backup.syncAlbums)); return Padding( padding: const EdgeInsets.only(left: 8.0), child: ListView( shrinkWrap: true, children: [ - StreamBuilder( - stream: Store.watch(StoreKey.syncAlbums), - initialData: Store.tryGet(StoreKey.syncAlbums) ?? false, - builder: (context, snapshot) { - final albumSyncEnable = snapshot.data ?? false; - return Column( - children: [ - SettingListTile( - title: "sync_albums".t(context: context), - subtitle: "sync_upload_album_setting_subtitle".t(context: context), - trailing: Switch( - value: albumSyncEnable, - onChanged: (bool newValue) async { - await ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.syncAlbums, newValue); + Column( + children: [ + SettingListTile( + title: "sync_albums".t(context: context), + subtitle: "sync_upload_album_setting_subtitle".t(context: context), + trailing: Switch( + value: albumSyncEnable, + onChanged: (bool newValue) async { + await ref.read(settingsProvider).write(.backupSyncAlbums, newValue); - if (newValue == true) { - await _manageLinkedAlbums(); - } - }, - ), - ), - AnimatedSize( - duration: const Duration(milliseconds: 300), - curve: Curves.easeInOut, - child: AnimatedOpacity( - duration: const Duration(milliseconds: 200), - opacity: albumSyncEnable ? 1.0 : 0.0, - child: albumSyncEnable - ? SettingListTile( - onTap: _manualSyncAlbums, - contentPadding: const EdgeInsets.only(left: 32, right: 16), - title: "organize_into_albums".t(context: context), - subtitle: "organize_into_albums_description".t(context: context), - trailing: isAlbumSyncInProgress - ? const SizedBox( - width: 32, - height: 32, - child: CircularProgressIndicator.adaptive(strokeWidth: 2), - ) - : IconButton( - onPressed: _manualSyncAlbums, - icon: const Icon(Icons.sync_rounded), - color: context.colorScheme.onSurface.withValues(alpha: 0.7), - iconSize: 20, - constraints: const BoxConstraints(minWidth: 32, minHeight: 32), - ), - ) - : const SizedBox.shrink(), - ), - ), - ], - ); - }, + if (newValue == true) { + await _manageLinkedAlbums(); + } + }, + ), + ), + AnimatedSize( + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + child: AnimatedOpacity( + duration: const Duration(milliseconds: 200), + opacity: albumSyncEnable ? 1.0 : 0.0, + child: albumSyncEnable + ? SettingListTile( + onTap: _manualSyncAlbums, + contentPadding: const EdgeInsets.only(left: 32, right: 16), + title: "organize_into_albums".t(context: context), + subtitle: "organize_into_albums_description".t(context: context), + trailing: isAlbumSyncInProgress + ? const SizedBox( + width: 32, + height: 32, + child: CircularProgressIndicator.adaptive(strokeWidth: 2), + ) + : IconButton( + onPressed: _manualSyncAlbums, + icon: const Icon(Icons.sync_rounded), + color: context.colorScheme.onSurface.withValues(alpha: 0.7), + iconSize: 20, + constraints: const BoxConstraints(minWidth: 32, minHeight: 32), + ), + ) + : const SizedBox.shrink(), + ), + ), + ], ), ], ), @@ -161,60 +157,34 @@ class _AlbumSyncActionButtonState extends ConsumerState<_AlbumSyncActionButton> } } -class _SettingsSwitchTile extends ConsumerStatefulWidget { - final AppSettingsEnum appSettingsEnum; +class _BackupSwitchTile extends ConsumerWidget { + final SettingsKey metadataKey; + final bool Function(AppConfig) selector; final String titleKey; final String subtitleKey; - final void Function(bool?)? onChanged; + final void Function(bool)? onChanged; - const _SettingsSwitchTile({ - required this.appSettingsEnum, + const _BackupSwitchTile({ + required this.metadataKey, + required this.selector, required this.titleKey, required this.subtitleKey, this.onChanged, }); @override - ConsumerState createState() => _SettingsSwitchTileState(); -} - -class _SettingsSwitchTileState extends ConsumerState<_SettingsSwitchTile> { - late final Stream valueStream; - late final StreamSubscription subscription; - - @override - void initState() { - super.initState(); - valueStream = Store.watch(widget.appSettingsEnum.storeKey).asBroadcastStream(); - subscription = valueStream.listen((value) { - widget.onChanged?.call(value); - }); - } - - @override - void dispose() { - subscription.cancel(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { + final value = ref.watch(appConfigProvider.select(selector)); return Padding( padding: const EdgeInsets.only(left: 8.0), child: SettingListTile( - title: widget.titleKey.t(context: context), - subtitle: widget.subtitleKey.t(context: context), - trailing: StreamBuilder( - stream: valueStream, - initialData: Store.tryGet(widget.appSettingsEnum.storeKey) ?? widget.appSettingsEnum.defaultValue, - builder: (context, snapshot) { - final value = snapshot.data ?? false; - return Switch( - value: value, - onChanged: (bool newValue) async { - await ref.read(appSettingsServiceProvider).setSetting(widget.appSettingsEnum, newValue); - }, - ); + title: titleKey.t(context: context), + subtitle: subtitleKey.t(context: context), + trailing: Switch( + value: value, + onChanged: (bool newValue) async { + await ref.read(settingsProvider).write(metadataKey, newValue); + onChanged?.call(newValue); }, ), ), @@ -222,26 +192,28 @@ class _SettingsSwitchTileState extends ConsumerState<_SettingsSwitchTile> { } } -class _UseWifiForUploadVideosButton extends ConsumerWidget { - const _UseWifiForUploadVideosButton(); +class _UseCellularForVideosButton extends StatelessWidget { + const _UseCellularForVideosButton(); @override - Widget build(BuildContext context, WidgetRef ref) { - return const _SettingsSwitchTile( - appSettingsEnum: AppSettingsEnum.useCellularForUploadVideos, + Widget build(BuildContext context) { + return _BackupSwitchTile( + metadataKey: SettingsKey.backupUseCellularForVideos, + selector: (c) => c.backup.useCellularForVideos, titleKey: "videos", subtitleKey: "network_requirement_videos_upload", ); } } -class _UseWifiForUploadPhotosButton extends ConsumerWidget { - const _UseWifiForUploadPhotosButton(); +class _UseCellularForPhotosButton extends StatelessWidget { + const _UseCellularForPhotosButton(); @override - Widget build(BuildContext context, WidgetRef ref) { - return const _SettingsSwitchTile( - appSettingsEnum: AppSettingsEnum.useCellularForUploadPhotos, + Widget build(BuildContext context) { + return _BackupSwitchTile( + metadataKey: SettingsKey.backupUseCellularForPhotos, + selector: (c) => c.backup.useCellularForPhotos, titleKey: "photos", subtitleKey: "network_requirement_photos_upload", ); @@ -253,29 +225,22 @@ class _BackupOnlyWhenChargingButton extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - return _SettingsSwitchTile( - appSettingsEnum: AppSettingsEnum.backupRequireCharging, + final fgService = ref.read(backgroundWorkerFgServiceProvider); + return _BackupSwitchTile( + metadataKey: SettingsKey.backupRequireCharging, + selector: (c) => c.backup.requireCharging, titleKey: "charging", subtitleKey: "charging_requirement_mobile_backup", onChanged: (value) { - ref.read(backgroundWorkerFgServiceProvider).configure(requireCharging: value ?? false); + fgService.configure(requireCharging: value); }, ); } } -class _BackupDelaySlider extends ConsumerStatefulWidget { +class _BackupDelaySlider extends ConsumerWidget { const _BackupDelaySlider(); - @override - ConsumerState<_BackupDelaySlider> createState() => _BackupDelaySliderState(); -} - -class _BackupDelaySliderState extends ConsumerState<_BackupDelaySlider> { - late final Stream valueStream; - late final StreamSubscription subscription; - late int currentValue; - static int backupDelayToSliderValue(int ms) => switch (ms) { 5 => 0, 30 => 1, @@ -298,30 +263,9 @@ class _BackupDelaySliderState extends ConsumerState<_BackupDelaySlider> { }; @override - void initState() { - super.initState(); - final initialValue = - Store.tryGet(AppSettingsEnum.backupTriggerDelay.storeKey) ?? AppSettingsEnum.backupTriggerDelay.defaultValue; - currentValue = backupDelayToSliderValue(initialValue); - - valueStream = Store.watch(AppSettingsEnum.backupTriggerDelay.storeKey).asBroadcastStream(); - subscription = valueStream.listen((value) { - if (mounted && value != null) { - setState(() { - currentValue = backupDelayToSliderValue(value); - }); - } - }); - } - - @override - void dispose() { - subscription.cancel(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { + final triggerDelay = ref.watch(appConfigProvider.select((c) => c.backup.triggerDelay)); + final currentValue = backupDelayToSliderValue(triggerDelay); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -336,14 +280,13 @@ class _BackupDelaySliderState extends ConsumerState<_BackupDelaySlider> { ), Slider( value: currentValue.toDouble(), - onChanged: (double v) { - setState(() { - currentValue = v.toInt(); - }); + onChanged: (double v) async { + final seconds = backupDelayToSeconds(v.toInt()); + await ref.read(settingsProvider).write(SettingsKey.backupTriggerDelay, seconds); }, onChangeEnd: (double v) async { - final milliseconds = backupDelayToSeconds(v.toInt()); - await ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.backupTriggerDelay, milliseconds); + final seconds = backupDelayToSeconds(v.toInt()); + await ref.read(settingsProvider).write(SettingsKey.backupTriggerDelay, seconds); }, max: 3.0, min: 0.0, diff --git a/mobile/lib/widgets/settings/free_up_space_settings.dart b/mobile/lib/widgets/settings/free_up_space_settings.dart index 01ee8426d0..da14933997 100644 --- a/mobile/lib/widgets/settings/free_up_space_settings.dart +++ b/mobile/lib/widgets/settings/free_up_space_settings.dart @@ -80,7 +80,9 @@ class _FreeUpSpaceSettingsState extends ConsumerState { bool _isPresetSelected(int? daysAgo) { final state = ref.read(cleanupProvider); - if (state.selectedDate == null) return false; + if (state.selectedDate == null) { + return false; + } final expectedDate = daysAgo != null ? DateTime.now().subtract(Duration(days: daysAgo)) : DateTime(2000); diff --git a/mobile/lib/widgets/settings/language_settings.dart b/mobile/lib/widgets/settings/language_settings.dart index d0b3ac0021..2482801923 100644 --- a/mobile/lib/widgets/settings/language_settings.dart +++ b/mobile/lib/widgets/settings/language_settings.dart @@ -1,12 +1,13 @@ import 'dart:async'; + +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:easy_localization/easy_localization.dart'; import 'package:immich_mobile/constants/locales.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/services/localization.service.dart'; -import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/widgets/common/search_field.dart'; class LanguageSettings extends HookConsumerWidget { @@ -84,7 +85,7 @@ class LanguageSettings extends HookConsumerWidget { padding: const EdgeInsets.all(8), itemCount: filteredLocaleEntries.value.length, itemExtent: 64.0, - cacheExtent: 100, + scrollCacheExtent: const .pixels(100), itemBuilder: (context, index) { final countryName = filteredLocaleEntries.value[index].key; final localeValue = filteredLocaleEntries.value[index].value; diff --git a/mobile/lib/widgets/settings/networking_settings/endpoint_input.dart b/mobile/lib/widgets/settings/networking_settings/endpoint_input.dart index 735971e0c2..e8310caed4 100644 --- a/mobile/lib/widgets/settings/networking_settings/endpoint_input.dart +++ b/mobile/lib/widgets/settings/networking_settings/endpoint_input.dart @@ -1,10 +1,10 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart'; import 'package:immich_mobile/providers/auth.provider.dart'; import 'package:immich_mobile/widgets/settings/networking_settings/networking_settings.dart'; +import 'package:immich_ui/immich_ui.dart'; class EndpointInput extends StatefulHookConsumerWidget { const EndpointInput({ @@ -111,28 +111,12 @@ class EndpointInputState extends ConsumerState { status: auxCheckStatus, enabled: widget.enabled, ), - subtitle: TextFormField( + subtitle: ImmichURLInput( enabled: widget.enabled, - onTapOutside: (_) => focusNode.unfocus(), - autovalidateMode: AutovalidateMode.onUserInteraction, + autovalidateMode: .onUserInteraction, validator: validateUrl, - keyboardType: TextInputType.url, - style: const TextStyle(fontFamily: 'GoogleSansCode', fontSize: 14), - decoration: InputDecoration( - hintText: 'http(s)://immich.domain.com', - contentPadding: const EdgeInsets.all(16), - filled: true, - fillColor: context.colorScheme.surfaceContainer, - border: const OutlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(16))), - errorBorder: OutlineInputBorder( - borderSide: BorderSide(color: Colors.red[300]!), - borderRadius: const BorderRadius.all(Radius.circular(16)), - ), - disabledBorder: OutlineInputBorder( - borderSide: BorderSide(color: context.isDarkTheme ? Colors.grey[900]! : Colors.grey[300]!), - borderRadius: const BorderRadius.all(Radius.circular(16)), - ), - ), + keyboardAction: .next, + hintText: 'http(s)://immich.domain.com', controller: controller, focusNode: focusNode, ), diff --git a/mobile/lib/widgets/settings/networking_settings/external_network_preference.dart b/mobile/lib/widgets/settings/networking_settings/external_network_preference.dart index ba21acf49c..492a13de93 100644 --- a/mobile/lib/widgets/settings/networking_settings/external_network_preference.dart +++ b/mobile/lib/widgets/settings/networking_settings/external_network_preference.dart @@ -1,13 +1,11 @@ -import 'dart:convert'; - import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart' hide Store; +import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/store.model.dart'; -import 'package:immich_mobile/entities/store.entity.dart'; +import 'package:immich_mobile/domain/models/settings_key.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart'; +import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; import 'package:immich_mobile/widgets/settings/networking_settings/endpoint_input.dart'; class ExternalNetworkPreference extends HookConsumerWidget { @@ -23,11 +21,12 @@ class ExternalNetworkPreference extends HookConsumerWidget { saveEndpointList() { canSave.value = entries.value.every((e) => e.status == AuxCheckStatus.valid); - final endpointList = entries.value.where((url) => url.status == AuxCheckStatus.valid).toList(); + final urls = entries.value + .where((e) => e.status == AuxCheckStatus.valid && e.url.isNotEmpty) + .map((e) => e.url) + .toList(); - final jsonString = jsonEncode(endpointList); - - Store.put(StoreKey.externalEndpointList, jsonString); + ref.read(settingsProvider).write(SettingsKey.networkExternalEndpointList, urls); } updateValidationStatus(String url, int index, AuxCheckStatus status) { @@ -37,10 +36,6 @@ class ExternalNetworkPreference extends HookConsumerWidget { } handleReorder(int oldIndex, int newIndex) { - if (oldIndex < newIndex) { - newIndex -= 1; - } - final entry = entries.value.removeAt(oldIndex); entries.value.insert(newIndex, entry); entries.value = [...entries.value]; @@ -69,14 +64,13 @@ class ExternalNetworkPreference extends HookConsumerWidget { } useEffect(() { - final jsonString = Store.tryGet(StoreKey.externalEndpointList); + final urls = ref.read(appConfigProvider).network.externalEndpointList; - if (jsonString == null) { + if (urls.isEmpty) { return null; } - final List jsonList = jsonDecode(jsonString); - entries.value = jsonList.map((e) => AuxilaryEndpoint.fromJson(e)).toList(); + entries.value = urls.map((url) => AuxilaryEndpoint(url: url, status: .valid)).toList(); return null; }, const []); @@ -115,7 +109,7 @@ class ExternalNetworkPreference extends HookConsumerWidget { shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), itemCount: entries.value.length, - onReorder: handleReorder, + onReorderItem: handleReorder, itemBuilder: (context, index) { return EndpointInput( key: Key(index.toString()), diff --git a/mobile/lib/widgets/settings/networking_settings/local_network_preference.dart b/mobile/lib/widgets/settings/networking_settings/local_network_preference.dart index c89c8e149e..1d2b5eea0f 100644 --- a/mobile/lib/widgets/settings/networking_settings/local_network_preference.dart +++ b/mobile/lib/widgets/settings/networking_settings/local_network_preference.dart @@ -8,24 +8,29 @@ import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/providers/auth.provider.dart'; import 'package:immich_mobile/providers/network.provider.dart'; +import 'package:immich_ui/immich_ui.dart'; class LocalNetworkPreference extends HookConsumerWidget { const LocalNetworkPreference({super.key, required this.enabled}); final bool enabled; - Future _showEditDialog(BuildContext context, String title, String hintText, String initialValue) { + Future _showEditDialog( + BuildContext context, + String title, + String hintText, + String initialValue, { + bool isUrlField = false, + }) { final controller = TextEditingController(text: initialValue); return showDialog( context: context, builder: (context) => AlertDialog( title: Text(title), - content: TextField( - controller: controller, - autofocus: true, - decoration: InputDecoration(border: const OutlineInputBorder(), hintText: hintText), - ), + content: isUrlField + ? ImmichURLInput(controller: controller, autofocus: true, keyboardAction: .done, hintText: hintText) + : ImmichTextInput(controller: controller, autofocus: true, keyboardAction: .done, hintText: hintText), actions: [ TextButton( onPressed: () => Navigator.pop(context), @@ -81,6 +86,7 @@ class LocalNetworkPreference extends HookConsumerWidget { "server_endpoint".tr(), "http://local-ip:2283", localEndpointText.value, + isUrlField: true, ); if (localEndpoint != null) { diff --git a/mobile/lib/widgets/settings/networking_settings/networking_settings.dart b/mobile/lib/widgets/settings/networking_settings/networking_settings.dart index 981bec2c0c..7e6e169de7 100644 --- a/mobile/lib/widgets/settings/networking_settings/networking_settings.dart +++ b/mobile/lib/widgets/settings/networking_settings/networking_settings.dart @@ -1,13 +1,12 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart' hide Store; +import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart'; +import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; import 'package:immich_mobile/providers/network.provider.dart'; -import 'package:immich_mobile/services/app_settings.service.dart'; -import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart'; import 'package:immich_mobile/utils/url_helper.dart'; import 'package:immich_mobile/widgets/settings/networking_settings/external_network_preference.dart'; import 'package:immich_mobile/widgets/settings/networking_settings/local_network_preference.dart'; @@ -20,7 +19,10 @@ class NetworkingSettings extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final currentEndpoint = getServerUrl(); - final featureEnabled = useAppSettingsState(AppSettingsEnum.autoEndpointSwitching); + final featureEnabled = useState(ref.read(appConfigProvider).network.autoEndpointSwitching); + useValueChanged(featureEnabled.value, (_, __) { + ref.read(settingsProvider).write(.networkAutoEndpointSwitching, featureEnabled.value); + }); Future checkWifiReadPermission() async { final [hasLocationInUse, hasLocationAlways] = await Future.wait([ diff --git a/mobile/lib/widgets/settings/notification_setting.dart b/mobile/lib/widgets/settings/notification_setting.dart index d9eab26bda..46120bb218 100644 --- a/mobile/lib/widgets/settings/notification_setting.dart +++ b/mobile/lib/widgets/settings/notification_setting.dart @@ -3,12 +3,8 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/providers/notification_permission.provider.dart'; -import 'package:immich_mobile/services/app_settings.service.dart'; -import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart'; import 'package:immich_mobile/widgets/settings/settings_button_list_tile.dart'; -import 'package:immich_mobile/widgets/settings/settings_slider_list_tile.dart'; import 'package:immich_mobile/widgets/settings/settings_sub_page_scaffold.dart'; -import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart'; import 'package:permission_handler/permission_handler.dart'; class NotificationSetting extends HookConsumerWidget { @@ -17,11 +13,6 @@ class NotificationSetting extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final permissionService = ref.watch(notificationPermissionProvider); - - final sliderValue = useAppSettingsState(AppSettingsEnum.uploadErrorNotificationGracePeriod); - final totalProgressValue = useAppSettingsState(AppSettingsEnum.backgroundBackupTotalProgress); - final singleProgressValue = useAppSettingsState(AppSettingsEnum.backgroundBackupSingleProgress); - final hasPermission = permissionService == PermissionStatus.granted; openAppNotificationSettings(BuildContext ctx) { @@ -44,8 +35,6 @@ class NotificationSetting extends HookConsumerWidget { ); } - final String formattedValue = _formatSliderValue(sliderValue.value.toDouble()); - final notificationSettings = [ if (!hasPermission) SettingsButtonListTile( @@ -60,44 +49,8 @@ class NotificationSetting extends HookConsumerWidget { } }), ), - SettingsSwitchListTile( - enabled: hasPermission, - valueNotifier: totalProgressValue, - title: 'setting_notifications_total_progress_title'.tr(), - subtitle: 'setting_notifications_total_progress_subtitle'.tr(), - ), - SettingsSwitchListTile( - enabled: hasPermission, - valueNotifier: singleProgressValue, - title: 'setting_notifications_single_progress_title'.tr(), - subtitle: 'setting_notifications_single_progress_subtitle'.tr(), - ), - SettingsSliderListTile( - enabled: hasPermission, - valueNotifier: sliderValue, - text: 'setting_notifications_notify_failures_grace_period'.tr(namedArgs: {'duration': formattedValue}), - maxValue: 5.0, - noDivisons: 5, - label: formattedValue, - ), ]; return SettingsSubPageScaffold(settings: notificationSettings); } } - -String _formatSliderValue(double v) { - if (v == 0.0) { - return 'setting_notifications_notify_immediately'.tr(); - } else if (v == 1.0) { - return 'setting_notifications_notify_minutes'.tr(namedArgs: {'count': '30'}); - } else if (v == 2.0) { - return 'setting_notifications_notify_hours'.tr(namedArgs: {'count': '2'}); - } else if (v == 3.0) { - return 'setting_notifications_notify_hours'.tr(namedArgs: {'count': '8'}); - } else if (v == 4.0) { - return 'setting_notifications_notify_hours'.tr(namedArgs: {'count': '24'}); - } else { - return 'setting_notifications_notify_never'.tr(); - } -} diff --git a/mobile/lib/widgets/settings/preference_settings/primary_color_setting.dart b/mobile/lib/widgets/settings/preference_settings/primary_color_setting.dart index 22c9154981..48d0ca672b 100644 --- a/mobile/lib/widgets/settings/preference_settings/primary_color_setting.dart +++ b/mobile/lib/widgets/settings/preference_settings/primary_color_setting.dart @@ -1,15 +1,13 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/colors.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/theme_extensions.dart'; +import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; import 'package:immich_mobile/providers/theme.provider.dart'; -import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/theme/color_scheme.dart'; import 'package:immich_mobile/theme/dynamic_theme.dart'; -import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart'; class PrimaryColorSetting extends HookConsumerWidget { const PrimaryColorSetting({super.key}); @@ -17,18 +15,10 @@ class PrimaryColorSetting extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final themeProvider = ref.read(immichThemeProvider); + final themeConfig = ref.watch(appConfigProvider.select((config) => config.theme)); - final primaryColorSetting = useAppSettingsState(AppSettingsEnum.primaryColor); - final systemPrimaryColorSetting = useAppSettingsState(AppSettingsEnum.dynamicTheme); - - final currentPreset = useValueNotifier(ref.read(immichThemePresetProvider)); const tileSize = 55.0; - useValueChanged( - primaryColorSetting.value, - (_, __) => currentPreset.value = ImmichColorPreset.values.firstWhere((e) => e.name == primaryColorSetting.value), - ); - void popBottomSheet() { Future.delayed(const Duration(milliseconds: 200), () { Navigator.pop(context); @@ -36,23 +26,18 @@ class PrimaryColorSetting extends HookConsumerWidget { } onUseSystemColorChange(bool newValue) { - systemPrimaryColorSetting.value = newValue; - ref.watch(dynamicThemeSettingProvider.notifier).state = newValue; - ref.invalidate(immichThemeProvider); + ref.read(settingsProvider).write(.themeDynamic, newValue); popBottomSheet(); } onPrimaryColorChange(ImmichColorPreset colorPreset) { - primaryColorSetting.value = colorPreset.name; - ref.watch(immichThemePresetProvider.notifier).state = colorPreset; - ref.invalidate(immichThemeProvider); + ref.read(settingsProvider).write(.themePrimaryColor, colorPreset); //turn off system color setting - if (systemPrimaryColorSetting.value) { - onUseSystemColorChange(false); - } else { - popBottomSheet(); + if (themeConfig.dynamicTheme) { + ref.read(settingsProvider).write(.themeDynamic, false); } + popBottomSheet(); } buildPrimaryColorTile({ @@ -122,7 +107,7 @@ class PrimaryColorSetting extends HookConsumerWidget { 'theme_setting_system_primary_color_title'.tr(), style: context.textTheme.bodyLarge?.copyWith(fontWeight: FontWeight.w500, height: 1.5), ), - value: systemPrimaryColorSetting.value, + value: themeConfig.dynamicTheme, onChanged: onUseSystemColorChange, ), ), @@ -140,7 +125,7 @@ class PrimaryColorSetting extends HookConsumerWidget { topColor: theme.light.primary, bottomColor: theme.dark.primary, tileSize: tileSize, - showSelector: currentPreset.value == preset && !systemPrimaryColorSetting.value, + showSelector: themeConfig.primaryColor == preset && !themeConfig.dynamicTheme, ), ); }).toList(), diff --git a/mobile/lib/widgets/settings/preference_settings/theme_setting.dart b/mobile/lib/widgets/settings/preference_settings/theme_setting.dart index fc20fb7bed..ffeeceae02 100644 --- a/mobile/lib/widgets/settings/preference_settings/theme_setting.dart +++ b/mobile/lib/widgets/settings/preference_settings/theme_setting.dart @@ -3,72 +3,48 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; -import 'package:immich_mobile/providers/theme.provider.dart'; -import 'package:immich_mobile/services/app_settings.service.dart'; +import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; import 'package:immich_mobile/widgets/settings/preference_settings/primary_color_setting.dart'; import 'package:immich_mobile/widgets/settings/setting_group_title.dart'; import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart'; -import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart'; class ThemeSetting extends HookConsumerWidget { const ThemeSetting({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { - final currentThemeString = useAppSettingsState(AppSettingsEnum.themeMode); - final currentTheme = useValueNotifier(ref.read(immichThemeModeProvider)); + final currentTheme = useState(ref.read(appConfigProvider.select((config) => config.theme.mode))); final isDarkTheme = useValueNotifier(currentTheme.value == ThemeMode.dark); final isSystemTheme = useValueNotifier(currentTheme.value == ThemeMode.system); - - final applyThemeToBackgroundSetting = useAppSettingsState(AppSettingsEnum.colorfulInterface); - final applyThemeToBackgroundProvider = useValueNotifier(ref.read(colorfulInterfaceSettingProvider)); - - useValueChanged( - currentThemeString.value, - (_, __) => currentTheme.value = switch (currentThemeString.value) { - "light" => ThemeMode.light, - "dark" => ThemeMode.dark, - _ => ThemeMode.system, - }, - ); - - useValueChanged( - applyThemeToBackgroundSetting.value, - (_, __) => applyThemeToBackgroundProvider.value = applyThemeToBackgroundSetting.value, + final colorfulInterface = useValueNotifier( + ref.watch(appConfigProvider.select((config) => config.theme.colorfulInterface)), ); void onThemeChange(bool isDark) { - if (isDark) { - ref.watch(immichThemeModeProvider.notifier).state = ThemeMode.dark; - currentThemeString.value = "dark"; - } else { - ref.watch(immichThemeModeProvider.notifier).state = ThemeMode.light; - currentThemeString.value = "light"; - } + currentTheme.value = isDark ? ThemeMode.dark : ThemeMode.light; + ref.read(settingsProvider).write(.themeMode, currentTheme.value); } void onSystemThemeChange(bool isSystem) { if (isSystem) { - currentThemeString.value = "system"; + currentTheme.value = ThemeMode.system; isSystemTheme.value = true; - ref.watch(immichThemeModeProvider.notifier).state = ThemeMode.system; } else { final currentSystemBrightness = context.platformBrightness; isSystemTheme.value = false; isDarkTheme.value = currentSystemBrightness == Brightness.dark; if (currentSystemBrightness == Brightness.light) { - currentThemeString.value = "light"; - ref.watch(immichThemeModeProvider.notifier).state = ThemeMode.light; + currentTheme.value = ThemeMode.light; } else if (currentSystemBrightness == Brightness.dark) { - currentThemeString.value = "dark"; - ref.watch(immichThemeModeProvider.notifier).state = ThemeMode.dark; + currentTheme.value = ThemeMode.dark; } } + ref.read(settingsProvider).write(.themeMode, currentTheme.value); } void onSurfaceColorSettingChange(bool useColorfulInterface) { - applyThemeToBackgroundSetting.value = useColorfulInterface; - ref.watch(colorfulInterfaceSettingProvider.notifier).state = useColorfulInterface; + ref.read(settingsProvider).write(.themeColorfulInterface, useColorfulInterface); + colorfulInterface.value = useColorfulInterface; } return Column( @@ -91,7 +67,7 @@ class ThemeSetting extends HookConsumerWidget { ), const PrimaryColorSetting(), SettingsSwitchListTile( - valueNotifier: applyThemeToBackgroundProvider, + valueNotifier: colorfulInterface, title: "theme_setting_colorful_interface_title".t(context: context), subtitle: 'theme_setting_colorful_interface_subtitle'.t(context: context), onChanged: onSurfaceColorSettingChange, diff --git a/mobile/lib/widgets/settings/settings_switch_list_tile.dart b/mobile/lib/widgets/settings/settings_switch_list_tile.dart index f5d6dfd05a..d8ed3ac017 100644 --- a/mobile/lib/widgets/settings/settings_switch_list_tile.dart +++ b/mobile/lib/widgets/settings/settings_switch_list_tile.dart @@ -29,7 +29,9 @@ class SettingsSwitchListTile extends StatelessWidget { @override Widget build(BuildContext context) { void onSwitchChanged(bool value) { - if (!enabled) return; + if (!enabled) { + return; + } valueNotifier.value = value; onChanged?.call(value); diff --git a/mobile/lib/widgets/shared_link/shared_link_item.dart b/mobile/lib/widgets/shared_link/shared_link_item.dart index 19da80b833..d419d6ead0 100644 --- a/mobile/lib/widgets/shared_link/shared_link_item.dart +++ b/mobile/lib/widgets/shared_link/shared_link_item.dart @@ -1,201 +1,140 @@ import 'dart:math' as math; import 'package:auto_route/auto_route.dart'; -import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/generated/translations.g.dart'; import 'package:immich_mobile/models/shared_link/shared_link.model.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; import 'package:immich_mobile/providers/shared_link.provider.dart'; import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/utils/debug_print.dart'; import 'package:immich_mobile/utils/image_url_builder.dart'; import 'package:immich_mobile/utils/url_helper.dart'; import 'package:immich_mobile/widgets/common/confirm_dialog.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; import 'package:immich_mobile/widgets/search/thumbnail_with_info.dart'; -import 'package:immich_mobile/utils/debug_print.dart'; class SharedLinkItem extends ConsumerWidget { final SharedLink sharedLink; const SharedLinkItem(this.sharedLink, {super.key}); - bool isExpired() { - if (sharedLink.expiresAt != null) { - return DateTime.now().isAfter(sharedLink.expiresAt!); - } - return false; - } + bool isExpired() => sharedLink.expiresAt?.isBefore(DateTime.now()) ?? false; + + Widget buildExpiryDuration(BuildContext context) { + var expiresText = context.t.shared_link_expires_never; + IconData expiryIcon = Icons.schedule; - Widget getExpiryDuration(bool isDarkMode) { - var expiresText = "shared_link_expires_never".tr(); if (sharedLink.expiresAt != null) { if (isExpired()) { - return Text("expired", style: TextStyle(color: Colors.red[300])).tr(); + expiresText = context.t.expired; + expiryIcon = Icons.timer_off_outlined; } + final difference = sharedLink.expiresAt!.difference(DateTime.now()); dPrint(() => "Difference: $difference"); + if (difference.inDays > 0) { var dayDifference = difference.inDays; if (difference.inHours % 24 > 12) { dayDifference += 1; } - expiresText = "shared_link_expires_days".tr(namedArgs: {'count': dayDifference.toString()}); + expiresText = context.t.shared_link_expires_days(count: dayDifference); } else if (difference.inHours > 0) { - expiresText = "shared_link_expires_hours".tr(namedArgs: {'count': difference.inHours.toString()}); + expiresText = context.t.shared_link_expires_hours(count: difference.inHours); } else if (difference.inMinutes > 0) { - expiresText = "shared_link_expires_minutes".tr(namedArgs: {'count': difference.inMinutes.toString()}); + expiresText = context.t.shared_link_expires_minutes(count: difference.inMinutes); } else if (difference.inSeconds > 0) { - expiresText = "shared_link_expires_seconds".tr(namedArgs: {'count': difference.inSeconds.toString()}); + expiresText = context.t.shared_link_expires_seconds(count: difference.inSeconds); } } - return Text(expiresText, style: TextStyle(color: isDarkMode ? Colors.grey[400] : Colors.grey[600])); + + return Row( + children: [ + Icon(expiryIcon, size: 12, color: isExpired() ? context.colorScheme.error : context.colorScheme.onSurface), + const SizedBox(width: 4), + Text( + expiresText, + style: TextStyle(color: isExpired() ? context.colorScheme.error : context.colorScheme.onSurface), + ), + ], + ); } @override Widget build(BuildContext context, WidgetRef ref) { - final colorScheme = context.colorScheme; - final isDarkMode = colorScheme.brightness == Brightness.dark; final thumbnailUrl = sharedLink.thumbAssetId != null ? getThumbnailUrlForRemoteId(sharedLink.thumbAssetId!) : null; final imageSize = math.min(context.width / 4, 100.0); - void copyShareLinkToClipboard() { + Future copyShareLinkToClipboard() async { final externalDomain = ref.read(serverInfoProvider.select((s) => s.serverConfig.externalDomain)); - var serverUrl = externalDomain.isNotEmpty ? externalDomain : getServerUrl(); - if (serverUrl != null && !serverUrl.endsWith('/')) { - serverUrl += '/'; - } - if (serverUrl == null) { + final serverUrl = externalDomain.isNotEmpty ? externalDomain : getServerUrl(); + final shareUrl = buildSharedLinkUrl(baseUrl: serverUrl, slug: sharedLink.slug, key: sharedLink.key); + + if (shareUrl == null) { ImmichToast.show( context: context, gravity: ToastGravity.BOTTOM, toastType: ToastType.error, - msg: "shared_link_error_server_url_fetch".tr(), + msg: context.t.shared_link_error_server_url_fetch, ); return; } - final hasSlug = sharedLink.slug?.isNotEmpty == true; - final urlPath = hasSlug ? sharedLink.slug : sharedLink.key; - final basePath = hasSlug ? 's' : 'share'; - Clipboard.setData(ClipboardData(text: "$serverUrl$basePath/$urlPath")).then((_) { - context.scaffoldMessenger.showSnackBar( - SnackBar( - content: Text( - "shared_link_clipboard_copied_massage", - style: context.textTheme.bodyLarge?.copyWith(color: context.primaryColor), - ).tr(), - duration: const Duration(seconds: 2), + await Clipboard.setData(ClipboardData(text: shareUrl)); + if (!context.mounted) { + return; + } + context.scaffoldMessenger.showSnackBar( + SnackBar( + content: Text( + context.t.shared_link_clipboard_copied_massage, + style: context.textTheme.bodyLarge?.copyWith(color: context.primaryColor), ), - ); - }); - } - - Future deleteShareLink() async { - return showDialog( - context: context, - builder: (BuildContext context) { - return ConfirmDialog( - title: "delete_shared_link_dialog_title", - content: "confirm_delete_shared_link", - onOk: () => ref.read(sharedLinksStateProvider.notifier).deleteLink(sharedLink.id), - ); - }, + duration: const Duration(seconds: 2), + ), ); } Widget buildThumbnail() { - if (thumbnailUrl == null) { - return Container( - height: imageSize * 1.2, - width: imageSize, - decoration: BoxDecoration(color: isDarkMode ? Colors.grey[800] : Colors.grey[200]), - child: Center( - child: Icon(Icons.image_not_supported_outlined, color: isDarkMode ? Colors.grey[100] : Colors.grey[700]), - ), - ); - } return SizedBox( height: imageSize * 1.2, width: imageSize, - child: Padding( - padding: const EdgeInsets.only(right: 4.0), - child: ThumbnailWithInfo( - imageUrl: thumbnailUrl, - key: key, - textInfo: '', - noImageIcon: Icons.image_not_supported_outlined, - onTap: () {}, - ), - ), + child: thumbnailUrl == null + ? const Card( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(10))), + child: Icon(Icons.image_not_supported_outlined), + ) + : ThumbnailWithInfo( + imageUrl: thumbnailUrl, + key: key, + textInfo: '', + noImageIcon: Icons.image_not_supported_outlined, + onTap: () => context.pushRoute(SharedLinkEditRoute(existingLink: sharedLink)), + ), ); } Widget buildInfoChip(String labelText) { - return Padding( - padding: const EdgeInsets.only(right: 10), - child: Chip( - backgroundColor: colorScheme.primary, - label: Text( - labelText, - style: TextStyle( - fontSize: 11, - fontWeight: FontWeight.w500, - color: isDarkMode ? Colors.black : Colors.white, - ), - ), - shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(25))), + return Card.outlined( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), + child: Text(labelText, style: const TextStyle(fontSize: 11)), ), ); } - Widget buildBottomInfo() { + Widget buildShareParameterInfos() { return Row( + spacing: 4, children: [ - if (sharedLink.allowUpload) buildInfoChip("upload".tr()), - if (sharedLink.allowDownload) buildInfoChip("download".tr()), - if (sharedLink.showMetadata) buildInfoChip("shared_link_info_chip_metadata".tr()), - ], - ); - } - - Widget buildSharedLinkActions() { - const actionIconSize = 20.0; - return Row( - children: [ - IconButton( - splashRadius: 25, - constraints: const BoxConstraints(), - iconSize: actionIconSize, - icon: const Icon(Icons.delete_outline), - style: const ButtonStyle( - tapTargetSize: MaterialTapTargetSize.shrinkWrap, // the '2023' part - ), - onPressed: deleteShareLink, - ), - IconButton( - splashRadius: 25, - constraints: const BoxConstraints(), - iconSize: actionIconSize, - icon: const Icon(Icons.edit_outlined), - style: const ButtonStyle( - tapTargetSize: MaterialTapTargetSize.shrinkWrap, // the '2023' part - ), - onPressed: () => context.pushRoute(SharedLinkEditRoute(existingLink: sharedLink)), - ), - IconButton( - splashRadius: 25, - constraints: const BoxConstraints(), - iconSize: actionIconSize, - icon: const Icon(Icons.copy_outlined), - style: const ButtonStyle( - tapTargetSize: MaterialTapTargetSize.shrinkWrap, // the '2023' part - ), - onPressed: copyShareLinkToClipboard, - ), + if (sharedLink.allowUpload) buildInfoChip(context.t.upload), + if (sharedLink.allowDownload) buildInfoChip(context.t.download), + if (sharedLink.showMetadata) buildInfoChip(context.t.shared_link_info_chip_metadata), ], ); } @@ -204,69 +143,64 @@ class SharedLinkItem extends ConsumerWidget { return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - getExpiryDuration(isDarkMode), - Padding( - padding: const EdgeInsets.only(top: 5), - child: Tooltip( - verticalOffset: 0, - decoration: BoxDecoration( - color: colorScheme.primary.withValues(alpha: 0.9), - borderRadius: const BorderRadius.all(Radius.circular(10)), - ), - textStyle: TextStyle(color: isDarkMode ? Colors.black : Colors.white, fontWeight: FontWeight.bold), - message: sharedLink.title, - preferBelow: false, - triggerMode: TooltipTriggerMode.tap, - child: Text( - sharedLink.title, - style: TextStyle( - color: colorScheme.primary, - fontWeight: FontWeight.bold, - overflow: TextOverflow.ellipsis, - ), - ), + const SizedBox(height: 5), + Text( + sharedLink.title, + style: TextStyle( + color: Theme.of(context).colorScheme.primary, + fontWeight: FontWeight.bold, + overflow: TextOverflow.ellipsis, ), ), - Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Tooltip( - verticalOffset: 0, - decoration: BoxDecoration( - color: colorScheme.primary.withValues(alpha: 0.9), - borderRadius: const BorderRadius.all(Radius.circular(10)), - ), - textStyle: TextStyle(color: isDarkMode ? Colors.black : Colors.white, fontWeight: FontWeight.bold), - message: sharedLink.description ?? "", - preferBelow: false, - triggerMode: TooltipTriggerMode.tap, - child: Text(sharedLink.description ?? "", overflow: TextOverflow.ellipsis), - ), - ), - Padding(padding: const EdgeInsets.only(right: 15), child: buildSharedLinkActions()), - ], - ), - buildBottomInfo(), + if (sharedLink.description?.isNotEmpty ?? false) + Text(sharedLink.description!, overflow: TextOverflow.ellipsis), + buildExpiryDuration(context), + buildShareParameterInfos(), ], ); } - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding(padding: const EdgeInsets.only(left: 15), child: buildThumbnail()), - Expanded( - child: Padding(padding: const EdgeInsets.only(left: 15), child: buildSharedLinkDetails()), - ), - ], + return Dismissible( + key: ValueKey(sharedLink.id), + direction: DismissDirection.endToStart, + background: Container( + color: Theme.of(context).colorScheme.error, + alignment: Alignment.centerRight, + padding: const EdgeInsets.only(right: 20), + child: Icon(Icons.delete, color: Theme.of(context).colorScheme.onError), + ), + confirmDismiss: (_) async { + final confirmed = await showDialog( + context: context, + builder: (BuildContext context) => ConfirmDialog( + title: "delete_shared_link_dialog_title", + content: "confirm_delete_shared_link", + onOk: () {}, + ), + ); + + if (confirmed == true) { + await ref.read(sharedLinksStateProvider.notifier).deleteLink(sharedLink.id); + return true; + } + + return false; + }, + child: InkWell( + onTap: () => context.pushRoute(SharedLinkEditRoute(existingLink: sharedLink)), + onLongPress: copyShareLinkToClipboard, + child: Padding( + padding: const EdgeInsets.all(12), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + buildThumbnail(), + const SizedBox(width: 12), + Expanded(child: buildSharedLinkDetails()), + ], + ), ), - const Padding(padding: EdgeInsets.all(20), child: Divider(height: 0)), - ], + ), ); } } diff --git a/mobile/lib/wm_executor.dart b/mobile/lib/wm_executor.dart index a10b651696..e873c5f76d 100644 --- a/mobile/lib/wm_executor.dart +++ b/mobile/lib/wm_executor.dart @@ -6,8 +6,8 @@ import 'dart:math'; import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; +import 'package:immich_mobile/utils/isolate_worker.dart'; import 'package:worker_manager/src/number_of_processors/processors_io.dart'; -import 'package:worker_manager/src/worker/worker.dart'; import 'package:worker_manager/worker_manager.dart'; final workerManagerPatch = _Executor(); @@ -16,6 +16,13 @@ final workerManagerPatch = _Executor(); const _minId = -9007199254740992; const _maxId = 9007199254740992; +class _GentleTask extends Task implements Gentle { + @override + final GentleExecution execution; + + _GentleTask({required super.id, required super.completer, required super.workPriority, required this.execution}); +} + class Mixinable { late final itSelf = this as T; } @@ -43,19 +50,21 @@ mixin _ExecutorLogger on Mixinable<_Executor> { } void logMessage(String message) { - if (log) print(message); + if (log) { + print(message); + } } } class _Executor extends Mixinable<_Executor> with _ExecutorLogger { final _queue = PriorityQueue(); - final _pool = []; + final _pool = []; var _nextTaskId = _minId; var _dynamicSpawning = false; var _isolatesCount = numberOfProcessors; @visibleForTesting - UnmodifiableListView get pool => UnmodifiableListView(_pool); + UnmodifiableListView get pool => UnmodifiableListView(_pool); @override Future init({int? isolatesCount, bool? dynamicSpawning}) async { @@ -78,117 +87,37 @@ class _Executor extends Mixinable<_Executor> with _ExecutorLogger { @override Future dispose() async { _queue.clear(); - for (final worker in _pool) { - if (worker.initialized || worker.initializing) { - worker.kill(); - } - } + final shutdown = _pool.map((worker) => worker.shutdown()).toList(growable: false); _pool.clear(); + await Future.wait(shutdown); super.dispose(); } - Cancelable execute(Execute execution, {WorkPriority priority = WorkPriority.immediately}) { - return _createCancelable(execution: execution, priority: priority); - } - - Cancelable executeNow(ExecuteGentle execution) { - final task = TaskGentle( - id: "", - workPriority: WorkPriority.immediately, - execution: execution, - completer: Completer(), - ); - - Future run() async { - try { - final result = await execution(() => task.canceled); - task.complete(result, null, null); - } catch (error, st) { - task.complete(null, error, st); - } - } - - run(); - return Cancelable(completer: task.completer, onCancel: () => _cancel(task)); - } - - Cancelable executeWithPort( - ExecuteWithPort execution, { - WorkPriority priority = WorkPriority.immediately, - required void Function(T value) onMessage, - }) { - return _createCancelable( - execution: execution, - priority: priority, - onMessage: (message) => onMessage(message as T), - ); - } - - Cancelable executeGentle(ExecuteGentle execution, {WorkPriority priority = WorkPriority.immediately}) { - return _createCancelable(execution: execution, priority: priority); - } - - Cancelable executeGentleWithPort( - ExecuteGentleWithPort execution, { - WorkPriority priority = WorkPriority.immediately, - required void Function(T value) onMessage, - }) { - return _createCancelable( - execution: execution, - priority: priority, - onMessage: (message) => onMessage(message as T), - ); - } - - void _createWorkers() { - for (var i = 0; i < _isolatesCount; i++) { - _pool.add(Worker()); - } - } - - Future _initializeWorkers() async { - await Future.wait(_pool.map((e) => e.initialize())); - } - - Cancelable _createCancelable({ - required Function execution, - WorkPriority priority = WorkPriority.immediately, - void Function(Object value)? onMessage, - }) { + /// Runs [execution] on a worker isolate; its [Completer] completes when the + /// returned [Cancelable] is cancelled. + Cancelable executeGentle(GentleExecution execution, {WorkPriority priority = WorkPriority.immediately}) { if (_nextTaskId + 1 == _maxId) { _nextTaskId = _minId; } final id = _nextTaskId.toString(); _nextTaskId++; - late final Task task; - final completer = Completer(); - if (execution is ExecuteWithPort) { - task = TaskWithPort( - id: id, - workPriority: priority, - execution: execution, - completer: completer, - onMessage: onMessage!, - ); - } else if (execution is ExecuteGentle) { - task = TaskGentle(id: id, workPriority: priority, execution: execution, completer: completer); - } else if (execution is ExecuteGentleWithPort) { - task = TaskGentleWithPort( - id: id, - workPriority: priority, - execution: execution, - completer: completer, - onMessage: onMessage!, - ); - } else if (execution is Execute) { - task = TaskRegular(id: id, workPriority: priority, execution: execution, completer: completer); - } + final task = _GentleTask(id: id, workPriority: priority, execution: execution, completer: Completer()); _queue.add(task); _schedule(); logTaskAdded(task.id); return Cancelable(completer: task.completer, onCancel: () => _cancel(task)); } + void _createWorkers() { + for (var i = 0; i < _isolatesCount; i++) { + _pool.add(IsolateWorker()); + } + } + + Future _initializeWorkers() async { + await Future.wait(_pool.map((e) => e.initialize())); + } + Future _ensureWorkersInitialized() async { if (_pool.isEmpty) { _createWorkers(); @@ -219,7 +148,9 @@ class _Executor extends Mixinable<_Executor> with _ExecutorLogger { _ensureWorkersInitialized(); return; } - if (_queue.isEmpty) return; + if (_queue.isEmpty) { + return; + } final task = _queue.removeFirst(); availableWorker @@ -235,7 +166,11 @@ class _Executor extends Mixinable<_Executor> with _ExecutorLogger { }, ) .whenComplete(() { - if (_dynamicSpawning && _queue.isEmpty) availableWorker.kill(); + if (_dynamicSpawning && _queue.isEmpty) { + // Retire the idle worker; shutdown() nulls its fields so the husk + // stays pooled and is revived by initialize() if work arrives. + unawaited(availableWorker.shutdown()); + } _schedule(); }); } @@ -244,13 +179,8 @@ class _Executor extends Mixinable<_Executor> with _ExecutorLogger { void _cancel(Task task) { task.cancel(); _queue.remove(task); - final targetWorker = _pool.firstWhereOrNull((worker) => worker.taskId == task.id); - if (task is Gentle) { - targetWorker?.cancelGentle(); - } else { - targetWorker?.kill(); - if (!_dynamicSpawning) targetWorker?.initialize(); - } + // All tasks are gentle: signal cancellation; the worker unwinds on its own. + _pool.firstWhereOrNull((worker) => worker.taskId == task.id)?.cancelGentle(); super._cancel(task); } } diff --git a/mobile/makefile b/mobile/makefile index 3a0a263687..645316efee 100644 --- a/mobile/makefile +++ b/mobile/makefile @@ -1,55 +1,26 @@ -.PHONY: build watch create_app_icon create_splash build_release_android pigeon test analyze format +.PHONY: build watch create_app_icon create_splash build_release_android pigeon test analyze format migration translation build: - dart run build_runner build --delete-conflicting-outputs -# Remove once auto_route updated to 10.1.0 - dart format lib/routing/router.gr.dart + @printf "This command has been removed. Please use:\n\n mise codegen # or mise //mobile:codegen:dart from another directory\n\n" >&2 && exit 1 pigeon: - dart run pigeon --input pigeon/native_sync_api.dart - dart run pigeon --input pigeon/local_image_api.dart - dart run pigeon --input pigeon/remote_image_api.dart - dart run pigeon --input pigeon/background_worker_api.dart - dart run pigeon --input pigeon/background_worker_lock_api.dart - dart run pigeon --input pigeon/connectivity_api.dart - dart run pigeon --input pigeon/network_api.dart - dart format lib/platform/native_sync_api.g.dart - dart format lib/platform/local_image_api.g.dart - dart format lib/platform/remote_image_api.g.dart - dart format lib/platform/background_worker_api.g.dart - dart format lib/platform/background_worker_lock_api.g.dart - dart format lib/platform/connectivity_api.g.dart - dart format lib/platform/network_api.g.dart + @printf "This command has been removed. Please use:\n\n mise pigeon # or mise //mobile:codegen:pigeon from another directory\n\n" >&2 && exit 1 -watch: - dart run build_runner watch --delete-conflicting-outputs - -create_app_icon: - flutter pub run flutter_launcher_icons:main - -create_splash: - flutter pub run flutter_native_splash:create build_release_android: - flutter build appbundle + @printf "This command has been removed. Please use:\n\n mise run build:android # or mise //mobile:build:android from another directory\n\n" >&2 && exit 1 migration: - dart run drift_dev make-migrations + @printf "This command has been removed. Please use:\n\n mise migration # or mise //mobile:drift:migration from another directory\n\n" >&2 && exit 1 translation: - pnpm --prefix ../i18n run format:fix - dart run easy_localization:generate -S ../i18n - dart run bin/generate_keys.dart - dart format lib/generated/codegen_loader.g.dart - dart format lib/generated/translations.g.dart + @printf "This command has been removed. Please use:\n\n mise translation # or mise //mobile:codegen:translation from another directory\n\n" >&2 && exit 1 analyze: - dart analyze --fatal-infos - dcm analyze lib --fatal-style --fatal-warnings + @printf "This command has been removed. Please use:\n\n mise analyze # or mise //mobile:lint from another directory\n\n" >&2 && exit 1 format: -# Ignore generated files manually until https://github.com/dart-lang/dart_style/issues/864 is resolved - dart format --set-exit-if-changed $$(find lib -name '*.dart' -not \( -name 'generated_plugin_registrant.dart' -o -name '*.g.dart' -o -name '*.drift.dart' \)) + @printf "This command has been removed. Please use:\n\n mise format # or mise //mobile:format from another directory\n\n" >&2 && exit 1 test: - flutter test + @printf "This command has been removed. Please use:\n\n mise test # or mise //mobile:test from another directory\n\n" >&2 && exit 1 diff --git a/mobile/mise.lock b/mobile/mise.lock new file mode 100644 index 0000000000..e95323e4d7 --- /dev/null +++ b/mobile/mise.lock @@ -0,0 +1,89 @@ +# @generated - this file is auto-generated by `mise lock` https://mise.en.dev/dev-tools/mise-lock.html + +[[tools."aqua:flutter/flutter"]] +version = "3.44.1" +backend = "aqua:flutter/flutter" + +[tools."aqua:flutter/flutter"."platforms.linux-arm64"] +url = "https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_3.44.1-stable.tar.xz" + +[tools."aqua:flutter/flutter"."platforms.linux-arm64-musl"] +url = "https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_3.44.1-stable.tar.xz" + +[tools."aqua:flutter/flutter"."platforms.linux-x64"] +url = "https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_3.44.1-stable.tar.xz" + +[tools."aqua:flutter/flutter"."platforms.linux-x64-musl"] +url = "https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_3.44.1-stable.tar.xz" + +[tools."aqua:flutter/flutter"."platforms.macos-arm64"] +url = "https://storage.googleapis.com/flutter_infra_release/releases/stable/macos/flutter_macos_arm64_3.44.1-stable.zip" + +[tools."aqua:flutter/flutter"."platforms.macos-x64"] +url = "https://storage.googleapis.com/flutter_infra_release/releases/stable/macos/flutter_macos_3.44.1-stable.zip" + +[tools."aqua:flutter/flutter"."platforms.windows-x64"] +url = "https://storage.googleapis.com/flutter_infra_release/releases/stable/windows/flutter_windows_3.44.1-stable.zip" + +[[tools."github:CQLabs/homebrew-dcm"]] +version = "1.37.0" +backend = "github:CQLabs/homebrew-dcm" + +[tools."github:CQLabs/homebrew-dcm"."platforms.linux-arm64"] +checksum = "sha256:253da2512b149913dfe345bf9a62a79acb2d730f66e71162ba4a92dfc4224b82" +url = "https://github.com/CQLabs/homebrew-dcm/releases/download/1.37.0/dcm-linux-arm-release.zip" +url_api = "https://api.github.com/repos/CQLabs/homebrew-dcm/releases/assets/404543838" + +[tools."github:CQLabs/homebrew-dcm"."platforms.linux-arm64-musl"] +checksum = "sha256:253da2512b149913dfe345bf9a62a79acb2d730f66e71162ba4a92dfc4224b82" +url = "https://github.com/CQLabs/homebrew-dcm/releases/download/1.37.0/dcm-linux-arm-release.zip" +url_api = "https://api.github.com/repos/CQLabs/homebrew-dcm/releases/assets/404543838" + +[tools."github:CQLabs/homebrew-dcm"."platforms.linux-x64"] +checksum = "sha256:477e086d4099c12f21e5ccd83b005d5fb945dd4cac4fd127fd9a08d7649af1cf" +url = "https://github.com/CQLabs/homebrew-dcm/releases/download/1.37.0/dcm-linux-x64-release.zip" +url_api = "https://api.github.com/repos/CQLabs/homebrew-dcm/releases/assets/404543797" + +[tools."github:CQLabs/homebrew-dcm"."platforms.linux-x64-musl"] +checksum = "sha256:477e086d4099c12f21e5ccd83b005d5fb945dd4cac4fd127fd9a08d7649af1cf" +url = "https://github.com/CQLabs/homebrew-dcm/releases/download/1.37.0/dcm-linux-x64-release.zip" +url_api = "https://api.github.com/repos/CQLabs/homebrew-dcm/releases/assets/404543797" + +[tools."github:CQLabs/homebrew-dcm"."platforms.macos-arm64"] +checksum = "sha256:30bede64367d09067093cc57af6ec9496d7717898138ded5cb98a16ac8dd9d93" +url = "https://github.com/CQLabs/homebrew-dcm/releases/download/1.37.0/dcm-macos-arm-release.zip" +url_api = "https://api.github.com/repos/CQLabs/homebrew-dcm/releases/assets/404543757" + +[tools."github:CQLabs/homebrew-dcm"."platforms.macos-x64"] +checksum = "sha256:e56cb99872be7445a4de1d37e5438ca70e3bcd83be7a2b9b385e3538881f8068" +url = "https://github.com/CQLabs/homebrew-dcm/releases/download/1.37.0/dcm-macos-x64-release.zip" +url_api = "https://api.github.com/repos/CQLabs/homebrew-dcm/releases/assets/404543727" + +[tools."github:CQLabs/homebrew-dcm"."platforms.windows-x64"] +checksum = "sha256:f133470daa3fb0427f039b424392af7e917d7e7db6b556aa2a968ab0e31587da" +url = "https://github.com/CQLabs/homebrew-dcm/releases/download/1.37.0/dcm-windows-release.zip" +url_api = "https://api.github.com/repos/CQLabs/homebrew-dcm/releases/assets/404543660" + +[[tools.java]] +version = "21.0.2" +backend = "core:java" + +[tools.java."platforms.linux-arm64"] +checksum = "sha256:08db1392a48d4eb5ea5315cf8f18b89dbaf36cda663ba882cf03c704c9257ec2" +url = "https://download.java.net/java/GA/jdk21.0.2/f2283984656d49d69e91c558476027ac/13/GPL/openjdk-21.0.2_linux-aarch64_bin.tar.gz" + +[tools.java."platforms.linux-x64"] +checksum = "sha256:a2def047a73941e01a73739f92755f86b895811afb1f91243db214cff5bdac3f" +url = "https://download.java.net/java/GA/jdk21.0.2/f2283984656d49d69e91c558476027ac/13/GPL/openjdk-21.0.2_linux-x64_bin.tar.gz" + +[tools.java."platforms.macos-arm64"] +checksum = "sha256:b3d588e16ec1e0ef9805d8a696591bd518a5cea62567da8f53b5ce32d11d22e4" +url = "https://download.java.net/java/GA/jdk21.0.2/f2283984656d49d69e91c558476027ac/13/GPL/openjdk-21.0.2_macos-aarch64_bin.tar.gz" + +[tools.java."platforms.macos-x64"] +checksum = "sha256:8fd09e15dc406387a0aba70bf5d99692874e999bf9cd9208b452b5d76ac922d3" +url = "https://download.java.net/java/GA/jdk21.0.2/f2283984656d49d69e91c558476027ac/13/GPL/openjdk-21.0.2_macos-x64_bin.tar.gz" + +[tools.java."platforms.windows-x64"] +checksum = "sha256:b6c17e747ae78cdd6de4d7532b3164b277daee97c007d3eaa2b39cca99882664" +url = "https://download.java.net/java/GA/jdk21.0.2/f2283984656d49d69e91c558476027ac/13/GPL/openjdk-21.0.2_windows-x64_bin.zip" diff --git a/mobile/mise.toml b/mobile/mise.toml index ed928f2445..a490f1aa4f 100644 --- a/mobile/mise.toml +++ b/mobile/mise.toml @@ -1,10 +1,18 @@ [tools] -flutter = "3.41.9" +"aqua:flutter/flutter" = "3.44.1" +java = "21.0.2" [tools."github:CQLabs/homebrew-dcm"] -version = "1.30.0" +version = "1.37.0" bin = "dcm" -postinstall = "chmod +x $MISE_TOOL_INSTALL_PATH/dcm" +postinstall = "chmod +x \"$MISE_TOOL_INSTALL_PATH/dcm\" || true" + +[tools."github:CQLabs/homebrew-dcm".platforms] +linux-x64 = { asset_pattern = "dcm-linux-x64-release.zip" } +linux-arm64 = { asset_pattern = "dcm-linux-arm-release.zip" } +macos-x64 = { asset_pattern = "dcm-macos-x64-release.zip" } +macos-arm64 = { asset_pattern = "dcm-macos-arm-release.zip" } +windows-x64 = { asset_pattern = "dcm-windows-release.zip" } [tasks."codegen:dart"] alias = "codegen" @@ -29,12 +37,16 @@ run = "dart run build_runner watch --delete-conflicting-outputs" [tasks."codegen:pigeon"] alias = "pigeon" description = "Generate pigeon platform code" -depends = [ - "pigeon:native-sync", - "pigeon:thumbnail", - "pigeon:background-worker", - "pigeon:background-worker-lock", - "pigeon:connectivity", +run = [ + "dart run pigeon --input pigeon/native_sync_api.dart", + "dart run pigeon --input pigeon/local_image_api.dart", + "dart run pigeon --input pigeon/remote_image_api.dart", + "dart run pigeon --input pigeon/background_worker_api.dart", + "dart run pigeon --input pigeon/background_worker_lock_api.dart", + "dart run pigeon --input pigeon/connectivity_api.dart", + "dart run pigeon --input pigeon/network_api.dart", + "dart run pigeon --input pigeon/view_intent_api.dart", + "dart format lib/platform/native_sync_api.g.dart lib/platform/local_image_api.g.dart lib/platform/remote_image_api.g.dart lib/platform/background_worker_api.g.dart lib/platform/background_worker_lock_api.g.dart lib/platform/connectivity_api.g.dart lib/platform/network_api.g.dart lib/platform/view_intent_api.g.dart", ] [tasks."codegen:translation"] @@ -60,13 +72,15 @@ run = "flutter pub run flutter_native_splash:create" description = "Run mobile tests" run = "flutter test" -[tasks.lint] +[tasks.analyze] +alias = "lint" description = "Analyze Dart code" depends = ["analyze:dart", "analyze:dcm"] -[tasks."lint-fix"] +[tasks."analyze-fix"] +alias = "lint-fix" description = "Auto-fix Dart code" -depends = ["analyze:fix:dart", "analyze:fix:dcm"] +depends = ["analyze-fix:dart", "analyze-fix:dcm"] [tasks.format] description = "Format Dart code" @@ -81,77 +95,17 @@ alias = "migration" description = "Generate database migrations" run = "dart run drift_dev make-migrations" +[tasks.install] +alias = "install" +description = "Install flutter dependencies" +run = "flutter pub get" + +[tasks.start] +alias = "start" +description = "Start flutter app" +run = "flutter run" # Internal tasks -[tasks."pigeon:native-sync"] -description = "Generate native sync API pigeon code" -hide = true -sources = ["pigeon/native_sync_api.dart"] -outputs = [ - "lib/platform/native_sync_api.g.dart", - "ios/Runner/Sync/Messages.g.swift", - "android/app/src/main/kotlin/app/alextran/immich/sync/Messages.g.kt", -] -run = [ - "dart run pigeon --input pigeon/native_sync_api.dart", - "dart format lib/platform/native_sync_api.g.dart", -] - -[tasks."pigeon:thumbnail"] -description = "Generate thumbnail API pigeon code" -hide = true -sources = ["pigeon/thumbnail_api.dart"] -outputs = [ - "lib/platform/thumbnail_api.g.dart", - "ios/Runner/Images/Thumbnails.g.swift", - "android/app/src/main/kotlin/app/alextran/immich/images/Thumbnails.g.kt", -] -run = [ - "dart run pigeon --input pigeon/thumbnail_api.dart", - "dart format lib/platform/thumbnail_api.g.dart", -] - -[tasks."pigeon:background-worker"] -description = "Generate background worker API pigeon code" -hide = true -sources = ["pigeon/background_worker_api.dart"] -outputs = [ - "lib/platform/background_worker_api.g.dart", - "ios/Runner/Background/BackgroundWorker.g.swift", - "android/app/src/main/kotlin/app/alextran/immich/background/BackgroundWorker.g.kt", -] -run = [ - "dart run pigeon --input pigeon/background_worker_api.dart", - "dart format lib/platform/background_worker_api.g.dart", -] - -[tasks."pigeon:background-worker-lock"] -description = "Generate background worker lock API pigeon code" -hide = true -sources = ["pigeon/background_worker_lock_api.dart"] -outputs = [ - "lib/platform/background_worker_lock_api.g.dart", - "android/app/src/main/kotlin/app/alextran/immich/background/BackgroundWorkerLock.g.kt", -] -run = [ - "dart run pigeon --input pigeon/background_worker_lock_api.dart", - "dart format lib/platform/background_worker_lock_api.g.dart", -] - -[tasks."pigeon:connectivity"] -description = "Generate connectivity API pigeon code" -hide = true -sources = ["pigeon/connectivity_api.dart"] -outputs = [ - "lib/platform/connectivity_api.g.dart", - "ios/Runner/Connectivity/Connectivity.g.swift", - "android/app/src/main/kotlin/app/alextran/immich/connectivity/Connectivity.g.kt", -] -run = [ - "dart run pigeon --input pigeon/connectivity_api.dart", - "dart format lib/platform/connectivity_api.g.dart", -] - [tasks."i18n:loader"] description = "Generate i18n loader" hide = true @@ -182,12 +136,23 @@ description = "Run Dart Code Metrics" hide = true run = "dcm analyze lib --fatal-style --fatal-warnings" -[tasks."analyze:fix:dart"] +[tasks."analyze-fix:dart"] description = "Auto-fix Dart analysis" hide = true run = "dart fix --apply" -[tasks."analyze:fix:dcm"] +[tasks."analyze-fix:dcm"] description = "Auto-fix Dart Code Metrics" hide = true run = "dcm fix lib" + + +[tasks.checklist] +run = [ + {task = "codegen:pigeon" }, + {task = "codegen:dart" }, + {task = "codegen:translation" }, + {task = "analyze" }, + {task = "format" }, + {task = "test" }, +] diff --git a/mobile/openapi/.openapi-generator/VERSION b/mobile/openapi/.openapi-generator/VERSION index 09a6d30847..696eaac5ce 100644 --- a/mobile/openapi/.openapi-generator/VERSION +++ b/mobile/openapi/.openapi-generator/VERSION @@ -1 +1 @@ -7.8.0 +7.22.0 diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index 8ac4e16cd5..cf0c18f34f 100644 --- a/mobile/openapi/README.md +++ b/mobile/openapi/README.md @@ -3,8 +3,8 @@ Immich API This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project: -- API version: 2.7.5 -- Generator version: 7.8.0 +- API version: 3.0.0 +- Generator version: 7.22.0 - Build package: org.openapitools.codegen.languages.DartClientCodegen ## Requirements @@ -103,12 +103,16 @@ Class | Method | HTTP request | Description *AssetsApi* | [**deleteBulkAssetMetadata**](doc//AssetsApi.md#deletebulkassetmetadata) | **DELETE** /assets/metadata | Delete asset metadata *AssetsApi* | [**downloadAsset**](doc//AssetsApi.md#downloadasset) | **GET** /assets/{id}/original | Download original asset *AssetsApi* | [**editAsset**](doc//AssetsApi.md#editasset) | **PUT** /assets/{id}/edits | Apply edits to an existing asset +*AssetsApi* | [**endSession**](doc//AssetsApi.md#endsession) | **DELETE** /assets/{id}/video/stream/{sessionId} | End HLS streaming session *AssetsApi* | [**getAssetEdits**](doc//AssetsApi.md#getassetedits) | **GET** /assets/{id}/edits | Retrieve edits for an existing asset *AssetsApi* | [**getAssetInfo**](doc//AssetsApi.md#getassetinfo) | **GET** /assets/{id} | Retrieve an asset *AssetsApi* | [**getAssetMetadata**](doc//AssetsApi.md#getassetmetadata) | **GET** /assets/{id}/metadata | Get asset metadata *AssetsApi* | [**getAssetMetadataByKey**](doc//AssetsApi.md#getassetmetadatabykey) | **GET** /assets/{id}/metadata/{key} | Retrieve asset metadata by key *AssetsApi* | [**getAssetOcr**](doc//AssetsApi.md#getassetocr) | **GET** /assets/{id}/ocr | Retrieve asset OCR data *AssetsApi* | [**getAssetStatistics**](doc//AssetsApi.md#getassetstatistics) | **GET** /assets/statistics | Get asset statistics +*AssetsApi* | [**getMainPlaylist**](doc//AssetsApi.md#getmainplaylist) | **GET** /assets/{id}/video/stream/main.m3u8 | Get HLS main playlist +*AssetsApi* | [**getMediaPlaylist**](doc//AssetsApi.md#getmediaplaylist) | **GET** /assets/{id}/video/stream/{sessionId}/{variantIndex}/playlist.m3u8 | Get HLS media playlist +*AssetsApi* | [**getSegment**](doc//AssetsApi.md#getsegment) | **GET** /assets/{id}/video/stream/{sessionId}/{variantIndex}/{filename} | Get HLS segment or init file *AssetsApi* | [**playAssetVideo**](doc//AssetsApi.md#playassetvideo) | **GET** /assets/{id}/video/playback | Play asset video *AssetsApi* | [**removeAssetEdits**](doc//AssetsApi.md#removeassetedits) | **DELETE** /assets/{id}/edits | Remove edits from an existing asset *AssetsApi* | [**runAssetJobs**](doc//AssetsApi.md#runassetjobs) | **POST** /assets/jobs | Run an asset job @@ -146,7 +150,7 @@ Class | Method | HTTP request | Description *DeprecatedApi* | [**runQueueCommandLegacy**](doc//DeprecatedApi.md#runqueuecommandlegacy) | **PUT** /jobs/{name} | Run jobs *DownloadApi* | [**downloadArchive**](doc//DownloadApi.md#downloadarchive) | **POST** /download/archive | Download asset archive *DownloadApi* | [**getDownloadInfo**](doc//DownloadApi.md#getdownloadinfo) | **POST** /download/info | Retrieve download information -*DuplicatesApi* | [**deleteDuplicate**](doc//DuplicatesApi.md#deleteduplicate) | **DELETE** /duplicates/{id} | Delete a duplicate +*DuplicatesApi* | [**deleteDuplicate**](doc//DuplicatesApi.md#deleteduplicate) | **DELETE** /duplicates/{id} | Dismiss a duplicate group *DuplicatesApi* | [**deleteDuplicates**](doc//DuplicatesApi.md#deleteduplicates) | **DELETE** /duplicates | Delete duplicates *DuplicatesApi* | [**getAssetDuplicates**](doc//DuplicatesApi.md#getassetduplicates) | **GET** /duplicates | Retrieve duplicates *DuplicatesApi* | [**resolveDuplicates**](doc//DuplicatesApi.md#resolveduplicates) | **POST** /duplicates/resolve | Resolve duplicate groups @@ -205,8 +209,9 @@ Class | Method | HTTP request | Description *PeopleApi* | [**updatePeople**](doc//PeopleApi.md#updatepeople) | **PUT** /people | Update people *PeopleApi* | [**updatePerson**](doc//PeopleApi.md#updateperson) | **PUT** /people/{id} | Update person *PluginsApi* | [**getPlugin**](doc//PluginsApi.md#getplugin) | **GET** /plugins/{id} | Retrieve a plugin -*PluginsApi* | [**getPluginTriggers**](doc//PluginsApi.md#getplugintriggers) | **GET** /plugins/triggers | List all plugin triggers -*PluginsApi* | [**getPlugins**](doc//PluginsApi.md#getplugins) | **GET** /plugins | List all plugins +*PluginsApi* | [**searchPluginMethods**](doc//PluginsApi.md#searchpluginmethods) | **GET** /plugins/methods | Retrieve plugin methods +*PluginsApi* | [**searchPluginTemplates**](doc//PluginsApi.md#searchplugintemplates) | **GET** /plugins/templates | Retrieve workflow templates +*PluginsApi* | [**searchPlugins**](doc//PluginsApi.md#searchplugins) | **GET** /plugins | List all plugins *QueuesApi* | [**emptyQueue**](doc//QueuesApi.md#emptyqueue) | **DELETE** /queues/{name}/jobs | Empty a queue *QueuesApi* | [**getQueue**](doc//QueuesApi.md#getqueue) | **GET** /queues/{name} | Retrieve a queue *QueuesApi* | [**getQueueJobs**](doc//QueuesApi.md#getqueuejobs) | **GET** /queues/{name}/jobs | Retrieve queue jobs @@ -314,7 +319,9 @@ Class | Method | HTTP request | Description *WorkflowsApi* | [**createWorkflow**](doc//WorkflowsApi.md#createworkflow) | **POST** /workflows | Create a workflow *WorkflowsApi* | [**deleteWorkflow**](doc//WorkflowsApi.md#deleteworkflow) | **DELETE** /workflows/{id} | Delete a workflow *WorkflowsApi* | [**getWorkflow**](doc//WorkflowsApi.md#getworkflow) | **GET** /workflows/{id} | Retrieve a workflow -*WorkflowsApi* | [**getWorkflows**](doc//WorkflowsApi.md#getworkflows) | **GET** /workflows | List all workflows +*WorkflowsApi* | [**getWorkflowForShare**](doc//WorkflowsApi.md#getworkflowforshare) | **GET** /workflows/{id}/share | Retrieve a workflow +*WorkflowsApi* | [**getWorkflowTriggers**](doc//WorkflowsApi.md#getworkflowtriggers) | **GET** /workflows/triggers | List all workflow triggers +*WorkflowsApi* | [**searchWorkflows**](doc//WorkflowsApi.md#searchworkflows) | **GET** /workflows | List all workflows *WorkflowsApi* | [**updateWorkflow**](doc//WorkflowsApi.md#updateworkflow) | **PUT** /workflows/{id} | Update a workflow @@ -357,7 +364,6 @@ Class | Method | HTTP request | Description - [AssetFaceResponseDto](doc//AssetFaceResponseDto.md) - [AssetFaceUpdateDto](doc//AssetFaceUpdateDto.md) - [AssetFaceUpdateItem](doc//AssetFaceUpdateItem.md) - - [AssetFaceWithoutPersonResponseDto](doc//AssetFaceWithoutPersonResponseDto.md) - [AssetIdErrorReason](doc//AssetIdErrorReason.md) - [AssetIdsDto](doc//AssetIdsDto.md) - [AssetIdsResponseDto](doc//AssetIdsResponseDto.md) @@ -376,6 +382,7 @@ Class | Method | HTTP request | Description - [AssetMetadataUpsertItemDto](doc//AssetMetadataUpsertItemDto.md) - [AssetOcrResponseDto](doc//AssetOcrResponseDto.md) - [AssetOrder](doc//AssetOrder.md) + - [AssetOrderBy](doc//AssetOrderBy.md) - [AssetRejectReason](doc//AssetRejectReason.md) - [AssetResponseDto](doc//AssetResponseDto.md) - [AssetStackResponseDto](doc//AssetStackResponseDto.md) @@ -483,21 +490,14 @@ Class | Method | HTTP request | Description - [PersonResponseDto](doc//PersonResponseDto.md) - [PersonStatisticsResponseDto](doc//PersonStatisticsResponseDto.md) - [PersonUpdateDto](doc//PersonUpdateDto.md) - - [PersonWithFacesResponseDto](doc//PersonWithFacesResponseDto.md) - [PinCodeChangeDto](doc//PinCodeChangeDto.md) - [PinCodeResetDto](doc//PinCodeResetDto.md) - [PinCodeSetupDto](doc//PinCodeSetupDto.md) - [PlacesResponseDto](doc//PlacesResponseDto.md) - - [PluginActionResponseDto](doc//PluginActionResponseDto.md) - - [PluginContextType](doc//PluginContextType.md) - - [PluginFilterResponseDto](doc//PluginFilterResponseDto.md) - - [PluginJsonSchema](doc//PluginJsonSchema.md) - - [PluginJsonSchemaProperty](doc//PluginJsonSchemaProperty.md) - - [PluginJsonSchemaPropertyAdditionalProperties](doc//PluginJsonSchemaPropertyAdditionalProperties.md) - - [PluginJsonSchemaType](doc//PluginJsonSchemaType.md) + - [PluginMethodResponseDto](doc//PluginMethodResponseDto.md) - [PluginResponseDto](doc//PluginResponseDto.md) - - [PluginTriggerResponseDto](doc//PluginTriggerResponseDto.md) - - [PluginTriggerType](doc//PluginTriggerType.md) + - [PluginTemplateResponseDto](doc//PluginTemplateResponseDto.md) + - [PluginTemplateStepResponseDto](doc//PluginTemplateStepResponseDto.md) - [PurchaseResponse](doc//PurchaseResponse.md) - [PurchaseUpdate](doc//PurchaseUpdate.md) - [QueueCommand](doc//QueueCommand.md) @@ -517,6 +517,9 @@ Class | Method | HTTP request | Description - [RatingsUpdate](doc//RatingsUpdate.md) - [ReactionLevel](doc//ReactionLevel.md) - [ReactionType](doc//ReactionType.md) + - [ReleaseChannel](doc//ReleaseChannel.md) + - [ReleaseEventV1](doc//ReleaseEventV1.md) + - [ReleaseType](doc//ReleaseType.md) - [ReverseGeocodingStateResponseDto](doc//ReverseGeocodingStateResponseDto.md) - [RotateParameters](doc//RotateParameters.md) - [SearchAlbumResponseDto](doc//SearchAlbumResponseDto.md) @@ -581,6 +584,7 @@ Class | Method | HTTP request | Description - [SyncAssetOcrDeleteV1](doc//SyncAssetOcrDeleteV1.md) - [SyncAssetOcrV1](doc//SyncAssetOcrV1.md) - [SyncAssetV1](doc//SyncAssetV1.md) + - [SyncAssetV2](doc//SyncAssetV2.md) - [SyncAuthUserV1](doc//SyncAuthUserV1.md) - [SyncEntityType](doc//SyncEntityType.md) - [SyncMemoryAssetDeleteV1](doc//SyncMemoryAssetDeleteV1.md) @@ -602,6 +606,7 @@ Class | Method | HTTP request | Description - [SystemConfigBackupsDto](doc//SystemConfigBackupsDto.md) - [SystemConfigDto](doc//SystemConfigDto.md) - [SystemConfigFFmpegDto](doc//SystemConfigFFmpegDto.md) + - [SystemConfigFFmpegRealtimeDto](doc//SystemConfigFFmpegRealtimeDto.md) - [SystemConfigFacesDto](doc//SystemConfigFacesDto.md) - [SystemConfigGeneratedFullsizeImageDto](doc//SystemConfigGeneratedFullsizeImageDto.md) - [SystemConfigGeneratedImageDto](doc//SystemConfigGeneratedImageDto.md) @@ -671,12 +676,14 @@ Class | Method | HTTP request | Description - [VersionCheckStateResponseDto](doc//VersionCheckStateResponseDto.md) - [VideoCodec](doc//VideoCodec.md) - [VideoContainer](doc//VideoContainer.md) - - [WorkflowActionItemDto](doc//WorkflowActionItemDto.md) - - [WorkflowActionResponseDto](doc//WorkflowActionResponseDto.md) - [WorkflowCreateDto](doc//WorkflowCreateDto.md) - - [WorkflowFilterItemDto](doc//WorkflowFilterItemDto.md) - - [WorkflowFilterResponseDto](doc//WorkflowFilterResponseDto.md) - [WorkflowResponseDto](doc//WorkflowResponseDto.md) + - [WorkflowShareResponseDto](doc//WorkflowShareResponseDto.md) + - [WorkflowShareStepDto](doc//WorkflowShareStepDto.md) + - [WorkflowStepDto](doc//WorkflowStepDto.md) + - [WorkflowTrigger](doc//WorkflowTrigger.md) + - [WorkflowTriggerResponseDto](doc//WorkflowTriggerResponseDto.md) + - [WorkflowType](doc//WorkflowType.md) - [WorkflowUpdateDto](doc//WorkflowUpdateDto.md) diff --git a/mobile/openapi/lib/api.dart b/mobile/openapi/lib/api.dart index 2d83e6ee6d..524cea4862 100644 --- a/mobile/openapi/lib/api.dart +++ b/mobile/openapi/lib/api.dart @@ -29,6 +29,7 @@ part 'auth/api_key_auth.dart'; part 'auth/oauth.dart'; part 'auth/http_basic_auth.dart'; part 'auth/http_bearer_auth.dart'; +part 'optional.dart'; part 'api/api_keys_api.dart'; part 'api/activities_api.dart'; @@ -105,7 +106,6 @@ part 'model/asset_face_delete_dto.dart'; part 'model/asset_face_response_dto.dart'; part 'model/asset_face_update_dto.dart'; part 'model/asset_face_update_item.dart'; -part 'model/asset_face_without_person_response_dto.dart'; part 'model/asset_id_error_reason.dart'; part 'model/asset_ids_dto.dart'; part 'model/asset_ids_response_dto.dart'; @@ -124,6 +124,7 @@ part 'model/asset_metadata_upsert_dto.dart'; part 'model/asset_metadata_upsert_item_dto.dart'; part 'model/asset_ocr_response_dto.dart'; part 'model/asset_order.dart'; +part 'model/asset_order_by.dart'; part 'model/asset_reject_reason.dart'; part 'model/asset_response_dto.dart'; part 'model/asset_stack_response_dto.dart'; @@ -231,21 +232,14 @@ part 'model/person_create_dto.dart'; part 'model/person_response_dto.dart'; part 'model/person_statistics_response_dto.dart'; part 'model/person_update_dto.dart'; -part 'model/person_with_faces_response_dto.dart'; part 'model/pin_code_change_dto.dart'; part 'model/pin_code_reset_dto.dart'; part 'model/pin_code_setup_dto.dart'; part 'model/places_response_dto.dart'; -part 'model/plugin_action_response_dto.dart'; -part 'model/plugin_context_type.dart'; -part 'model/plugin_filter_response_dto.dart'; -part 'model/plugin_json_schema.dart'; -part 'model/plugin_json_schema_property.dart'; -part 'model/plugin_json_schema_property_additional_properties.dart'; -part 'model/plugin_json_schema_type.dart'; +part 'model/plugin_method_response_dto.dart'; part 'model/plugin_response_dto.dart'; -part 'model/plugin_trigger_response_dto.dart'; -part 'model/plugin_trigger_type.dart'; +part 'model/plugin_template_response_dto.dart'; +part 'model/plugin_template_step_response_dto.dart'; part 'model/purchase_response.dart'; part 'model/purchase_update.dart'; part 'model/queue_command.dart'; @@ -265,6 +259,9 @@ part 'model/ratings_response.dart'; part 'model/ratings_update.dart'; part 'model/reaction_level.dart'; part 'model/reaction_type.dart'; +part 'model/release_channel.dart'; +part 'model/release_event_v1.dart'; +part 'model/release_type.dart'; part 'model/reverse_geocoding_state_response_dto.dart'; part 'model/rotate_parameters.dart'; part 'model/search_album_response_dto.dart'; @@ -329,6 +326,7 @@ part 'model/sync_asset_metadata_v1.dart'; part 'model/sync_asset_ocr_delete_v1.dart'; part 'model/sync_asset_ocr_v1.dart'; part 'model/sync_asset_v1.dart'; +part 'model/sync_asset_v2.dart'; part 'model/sync_auth_user_v1.dart'; part 'model/sync_entity_type.dart'; part 'model/sync_memory_asset_delete_v1.dart'; @@ -350,6 +348,7 @@ part 'model/sync_user_v1.dart'; part 'model/system_config_backups_dto.dart'; part 'model/system_config_dto.dart'; part 'model/system_config_f_fmpeg_dto.dart'; +part 'model/system_config_f_fmpeg_realtime_dto.dart'; part 'model/system_config_faces_dto.dart'; part 'model/system_config_generated_fullsize_image_dto.dart'; part 'model/system_config_generated_image_dto.dart'; @@ -419,12 +418,14 @@ part 'model/validate_library_response_dto.dart'; part 'model/version_check_state_response_dto.dart'; part 'model/video_codec.dart'; part 'model/video_container.dart'; -part 'model/workflow_action_item_dto.dart'; -part 'model/workflow_action_response_dto.dart'; part 'model/workflow_create_dto.dart'; -part 'model/workflow_filter_item_dto.dart'; -part 'model/workflow_filter_response_dto.dart'; part 'model/workflow_response_dto.dart'; +part 'model/workflow_share_response_dto.dart'; +part 'model/workflow_share_step_dto.dart'; +part 'model/workflow_step_dto.dart'; +part 'model/workflow_trigger.dart'; +part 'model/workflow_trigger_response_dto.dart'; +part 'model/workflow_type.dart'; part 'model/workflow_update_dto.dart'; diff --git a/mobile/openapi/lib/api/activities_api.dart b/mobile/openapi/lib/api/activities_api.dart index e0a393948c..490c418785 100644 --- a/mobile/openapi/lib/api/activities_api.dart +++ b/mobile/openapi/lib/api/activities_api.dart @@ -25,7 +25,7 @@ class ActivitiesApi { /// Parameters: /// /// * [ActivityCreateDto] activityCreateDto (required): - Future createActivityWithHttpInfo(ActivityCreateDto activityCreateDto,) async { + Future createActivityWithHttpInfo(ActivityCreateDto activityCreateDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/activities'; @@ -47,6 +47,7 @@ class ActivitiesApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -57,8 +58,8 @@ class ActivitiesApi { /// Parameters: /// /// * [ActivityCreateDto] activityCreateDto (required): - Future createActivity(ActivityCreateDto activityCreateDto,) async { - final response = await createActivityWithHttpInfo(activityCreateDto,); + Future createActivity(ActivityCreateDto activityCreateDto, { Future? abortTrigger, }) async { + final response = await createActivityWithHttpInfo(activityCreateDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -81,7 +82,7 @@ class ActivitiesApi { /// Parameters: /// /// * [String] id (required): - Future deleteActivityWithHttpInfo(String id,) async { + Future deleteActivityWithHttpInfo(String id, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/activities/{id}' .replaceAll('{id}', id); @@ -104,6 +105,7 @@ class ActivitiesApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -114,8 +116,8 @@ class ActivitiesApi { /// Parameters: /// /// * [String] id (required): - Future deleteActivity(String id,) async { - final response = await deleteActivityWithHttpInfo(id,); + Future deleteActivity(String id, { Future? abortTrigger, }) async { + final response = await deleteActivityWithHttpInfo(id, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -141,7 +143,7 @@ class ActivitiesApi { /// /// * [String] userId: /// Filter by user ID - Future getActivitiesWithHttpInfo(String albumId, { String? assetId, ReactionLevel? level, ReactionType? type, String? userId, }) async { + Future getActivitiesWithHttpInfo(String albumId, { String? assetId, ReactionLevel? level, ReactionType? type, String? userId, Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/activities'; @@ -177,6 +179,7 @@ class ActivitiesApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -198,8 +201,8 @@ class ActivitiesApi { /// /// * [String] userId: /// Filter by user ID - Future?> getActivities(String albumId, { String? assetId, ReactionLevel? level, ReactionType? type, String? userId, }) async { - final response = await getActivitiesWithHttpInfo(albumId, assetId: assetId, level: level, type: type, userId: userId, ); + Future?> getActivities(String albumId, { String? assetId, ReactionLevel? level, ReactionType? type, String? userId, Future? abortTrigger, }) async { + final response = await getActivitiesWithHttpInfo(albumId, assetId: assetId, level: level, type: type, userId: userId, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -229,7 +232,7 @@ class ActivitiesApi { /// /// * [String] assetId: /// Asset ID (if activity is for an asset) - Future getActivityStatisticsWithHttpInfo(String albumId, { String? assetId, }) async { + Future getActivityStatisticsWithHttpInfo(String albumId, { String? assetId, Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/activities/statistics'; @@ -256,6 +259,7 @@ class ActivitiesApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -270,8 +274,8 @@ class ActivitiesApi { /// /// * [String] assetId: /// Asset ID (if activity is for an asset) - Future getActivityStatistics(String albumId, { String? assetId, }) async { - final response = await getActivityStatisticsWithHttpInfo(albumId, assetId: assetId, ); + Future getActivityStatistics(String albumId, { String? assetId, Future? abortTrigger, }) async { + final response = await getActivityStatisticsWithHttpInfo(albumId, assetId: assetId, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } diff --git a/mobile/openapi/lib/api/albums_api.dart b/mobile/openapi/lib/api/albums_api.dart index d08d1cba9d..e6e7cdbf40 100644 --- a/mobile/openapi/lib/api/albums_api.dart +++ b/mobile/openapi/lib/api/albums_api.dart @@ -27,7 +27,7 @@ class AlbumsApi { /// * [String] id (required): /// /// * [BulkIdsDto] bulkIdsDto (required): - Future addAssetsToAlbumWithHttpInfo(String id, BulkIdsDto bulkIdsDto,) async { + Future addAssetsToAlbumWithHttpInfo(String id, BulkIdsDto bulkIdsDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/albums/{id}/assets' .replaceAll('{id}', id); @@ -50,6 +50,7 @@ class AlbumsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -62,8 +63,8 @@ class AlbumsApi { /// * [String] id (required): /// /// * [BulkIdsDto] bulkIdsDto (required): - Future?> addAssetsToAlbum(String id, BulkIdsDto bulkIdsDto,) async { - final response = await addAssetsToAlbumWithHttpInfo(id, bulkIdsDto,); + Future?> addAssetsToAlbum(String id, BulkIdsDto bulkIdsDto, { Future? abortTrigger, }) async { + final response = await addAssetsToAlbumWithHttpInfo(id, bulkIdsDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -89,7 +90,7 @@ class AlbumsApi { /// Parameters: /// /// * [AlbumsAddAssetsDto] albumsAddAssetsDto (required): - Future addAssetsToAlbumsWithHttpInfo(AlbumsAddAssetsDto albumsAddAssetsDto,) async { + Future addAssetsToAlbumsWithHttpInfo(AlbumsAddAssetsDto albumsAddAssetsDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/albums/assets'; @@ -111,6 +112,7 @@ class AlbumsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -121,8 +123,8 @@ class AlbumsApi { /// Parameters: /// /// * [AlbumsAddAssetsDto] albumsAddAssetsDto (required): - Future addAssetsToAlbums(AlbumsAddAssetsDto albumsAddAssetsDto,) async { - final response = await addAssetsToAlbumsWithHttpInfo(albumsAddAssetsDto,); + Future addAssetsToAlbums(AlbumsAddAssetsDto albumsAddAssetsDto, { Future? abortTrigger, }) async { + final response = await addAssetsToAlbumsWithHttpInfo(albumsAddAssetsDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -147,7 +149,7 @@ class AlbumsApi { /// * [String] id (required): /// /// * [AddUsersDto] addUsersDto (required): - Future addUsersToAlbumWithHttpInfo(String id, AddUsersDto addUsersDto,) async { + Future addUsersToAlbumWithHttpInfo(String id, AddUsersDto addUsersDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/albums/{id}/users' .replaceAll('{id}', id); @@ -170,6 +172,7 @@ class AlbumsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -182,8 +185,8 @@ class AlbumsApi { /// * [String] id (required): /// /// * [AddUsersDto] addUsersDto (required): - Future addUsersToAlbum(String id, AddUsersDto addUsersDto,) async { - final response = await addUsersToAlbumWithHttpInfo(id, addUsersDto,); + Future addUsersToAlbum(String id, AddUsersDto addUsersDto, { Future? abortTrigger, }) async { + final response = await addUsersToAlbumWithHttpInfo(id, addUsersDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -206,7 +209,7 @@ class AlbumsApi { /// Parameters: /// /// * [CreateAlbumDto] createAlbumDto (required): - Future createAlbumWithHttpInfo(CreateAlbumDto createAlbumDto,) async { + Future createAlbumWithHttpInfo(CreateAlbumDto createAlbumDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/albums'; @@ -228,6 +231,7 @@ class AlbumsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -238,8 +242,8 @@ class AlbumsApi { /// Parameters: /// /// * [CreateAlbumDto] createAlbumDto (required): - Future createAlbum(CreateAlbumDto createAlbumDto,) async { - final response = await createAlbumWithHttpInfo(createAlbumDto,); + Future createAlbum(CreateAlbumDto createAlbumDto, { Future? abortTrigger, }) async { + final response = await createAlbumWithHttpInfo(createAlbumDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -262,7 +266,7 @@ class AlbumsApi { /// Parameters: /// /// * [String] id (required): - Future deleteAlbumWithHttpInfo(String id,) async { + Future deleteAlbumWithHttpInfo(String id, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/albums/{id}' .replaceAll('{id}', id); @@ -285,6 +289,7 @@ class AlbumsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -295,8 +300,8 @@ class AlbumsApi { /// Parameters: /// /// * [String] id (required): - Future deleteAlbum(String id,) async { - final response = await deleteAlbumWithHttpInfo(id,); + Future deleteAlbum(String id, { Future? abortTrigger, }) async { + final response = await deleteAlbumWithHttpInfo(id, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -315,7 +320,7 @@ class AlbumsApi { /// * [String] key: /// /// * [String] slug: - Future getAlbumInfoWithHttpInfo(String id, { String? key, String? slug, }) async { + Future getAlbumInfoWithHttpInfo(String id, { String? key, String? slug, Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/albums/{id}' .replaceAll('{id}', id); @@ -345,6 +350,7 @@ class AlbumsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -359,8 +365,8 @@ class AlbumsApi { /// * [String] key: /// /// * [String] slug: - Future getAlbumInfo(String id, { String? key, String? slug, }) async { - final response = await getAlbumInfoWithHttpInfo(id, key: key, slug: slug, ); + Future getAlbumInfo(String id, { String? key, String? slug, Future? abortTrigger, }) async { + final response = await getAlbumInfoWithHttpInfo(id, key: key, slug: slug, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -387,7 +393,7 @@ class AlbumsApi { /// * [String] key: /// /// * [String] slug: - Future getAlbumMapMarkersWithHttpInfo(String id, { String? key, String? slug, }) async { + Future getAlbumMapMarkersWithHttpInfo(String id, { String? key, String? slug, Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/albums/{id}/map-markers' .replaceAll('{id}', id); @@ -417,6 +423,7 @@ class AlbumsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -431,8 +438,8 @@ class AlbumsApi { /// * [String] key: /// /// * [String] slug: - Future?> getAlbumMapMarkers(String id, { String? key, String? slug, }) async { - final response = await getAlbumMapMarkersWithHttpInfo(id, key: key, slug: slug, ); + Future?> getAlbumMapMarkers(String id, { String? key, String? slug, Future? abortTrigger, }) async { + final response = await getAlbumMapMarkersWithHttpInfo(id, key: key, slug: slug, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -454,7 +461,7 @@ class AlbumsApi { /// Returns statistics about the albums available to the authenticated user. /// /// Note: This method returns the HTTP [Response]. - Future getAlbumStatisticsWithHttpInfo() async { + Future getAlbumStatisticsWithHttpInfo({ Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/albums/statistics'; @@ -476,14 +483,15 @@ class AlbumsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } /// Retrieve album statistics /// /// Returns statistics about the albums available to the authenticated user. - Future getAlbumStatistics() async { - final response = await getAlbumStatisticsWithHttpInfo(); + Future getAlbumStatistics({ Future? abortTrigger, }) async { + final response = await getAlbumStatisticsWithHttpInfo(abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -506,11 +514,20 @@ class AlbumsApi { /// Parameters: /// /// * [String] assetId: - /// Filter albums containing this asset ID (ignores shared parameter) + /// Filter albums containing this asset ID (ignores other parameters) /// - /// * [bool] shared: - /// Filter by shared status: true = only shared, false = not shared, undefined = all owned albums - Future getAllAlbumsWithHttpInfo({ String? assetId, bool? shared, }) async { + /// * [String] id: + /// Album ID + /// + /// * [bool] isOwned: + /// Filter by ownership: true = only owned, false = only shared-with-me, undefined = no filter + /// + /// * [bool] isShared: + /// Filter by shared status: true = only shared, false = not shared, undefined = no filter + /// + /// * [String] name: + /// Album name (exact match) + Future getAllAlbumsWithHttpInfo({ String? assetId, String? id, bool? isOwned, bool? isShared, String? name, Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/albums'; @@ -524,8 +541,17 @@ class AlbumsApi { if (assetId != null) { queryParams.addAll(_queryParams('', 'assetId', assetId)); } - if (shared != null) { - queryParams.addAll(_queryParams('', 'shared', shared)); + if (id != null) { + queryParams.addAll(_queryParams('', 'id', id)); + } + if (isOwned != null) { + queryParams.addAll(_queryParams('', 'isOwned', isOwned)); + } + if (isShared != null) { + queryParams.addAll(_queryParams('', 'isShared', isShared)); + } + if (name != null) { + queryParams.addAll(_queryParams('', 'name', name)); } const contentTypes = []; @@ -539,6 +565,7 @@ class AlbumsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -549,12 +576,21 @@ class AlbumsApi { /// Parameters: /// /// * [String] assetId: - /// Filter albums containing this asset ID (ignores shared parameter) + /// Filter albums containing this asset ID (ignores other parameters) /// - /// * [bool] shared: - /// Filter by shared status: true = only shared, false = not shared, undefined = all owned albums - Future?> getAllAlbums({ String? assetId, bool? shared, }) async { - final response = await getAllAlbumsWithHttpInfo( assetId: assetId, shared: shared, ); + /// * [String] id: + /// Album ID + /// + /// * [bool] isOwned: + /// Filter by ownership: true = only owned, false = only shared-with-me, undefined = no filter + /// + /// * [bool] isShared: + /// Filter by shared status: true = only shared, false = not shared, undefined = no filter + /// + /// * [String] name: + /// Album name (exact match) + Future?> getAllAlbums({ String? assetId, String? id, bool? isOwned, bool? isShared, String? name, Future? abortTrigger, }) async { + final response = await getAllAlbumsWithHttpInfo(assetId: assetId, id: id, isOwned: isOwned, isShared: isShared, name: name, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -582,7 +618,7 @@ class AlbumsApi { /// * [String] id (required): /// /// * [BulkIdsDto] bulkIdsDto (required): - Future removeAssetFromAlbumWithHttpInfo(String id, BulkIdsDto bulkIdsDto,) async { + Future removeAssetFromAlbumWithHttpInfo(String id, BulkIdsDto bulkIdsDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/albums/{id}/assets' .replaceAll('{id}', id); @@ -605,6 +641,7 @@ class AlbumsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -617,8 +654,8 @@ class AlbumsApi { /// * [String] id (required): /// /// * [BulkIdsDto] bulkIdsDto (required): - Future?> removeAssetFromAlbum(String id, BulkIdsDto bulkIdsDto,) async { - final response = await removeAssetFromAlbumWithHttpInfo(id, bulkIdsDto,); + Future?> removeAssetFromAlbum(String id, BulkIdsDto bulkIdsDto, { Future? abortTrigger, }) async { + final response = await removeAssetFromAlbumWithHttpInfo(id, bulkIdsDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -646,7 +683,7 @@ class AlbumsApi { /// * [String] id (required): /// /// * [String] userId (required): - Future removeUserFromAlbumWithHttpInfo(String id, String userId,) async { + Future removeUserFromAlbumWithHttpInfo(String id, String userId, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/albums/{id}/user/{userId}' .replaceAll('{id}', id) @@ -670,6 +707,7 @@ class AlbumsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -682,8 +720,8 @@ class AlbumsApi { /// * [String] id (required): /// /// * [String] userId (required): - Future removeUserFromAlbum(String id, String userId,) async { - final response = await removeUserFromAlbumWithHttpInfo(id, userId,); + Future removeUserFromAlbum(String id, String userId, { Future? abortTrigger, }) async { + final response = await removeUserFromAlbumWithHttpInfo(id, userId, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -700,7 +738,7 @@ class AlbumsApi { /// * [String] id (required): /// /// * [UpdateAlbumDto] updateAlbumDto (required): - Future updateAlbumInfoWithHttpInfo(String id, UpdateAlbumDto updateAlbumDto,) async { + Future updateAlbumInfoWithHttpInfo(String id, UpdateAlbumDto updateAlbumDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/albums/{id}' .replaceAll('{id}', id); @@ -723,6 +761,7 @@ class AlbumsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -735,8 +774,8 @@ class AlbumsApi { /// * [String] id (required): /// /// * [UpdateAlbumDto] updateAlbumDto (required): - Future updateAlbumInfo(String id, UpdateAlbumDto updateAlbumDto,) async { - final response = await updateAlbumInfoWithHttpInfo(id, updateAlbumDto,); + Future updateAlbumInfo(String id, UpdateAlbumDto updateAlbumDto, { Future? abortTrigger, }) async { + final response = await updateAlbumInfoWithHttpInfo(id, updateAlbumDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -763,7 +802,7 @@ class AlbumsApi { /// * [String] userId (required): /// /// * [UpdateAlbumUserDto] updateAlbumUserDto (required): - Future updateAlbumUserWithHttpInfo(String id, String userId, UpdateAlbumUserDto updateAlbumUserDto,) async { + Future updateAlbumUserWithHttpInfo(String id, String userId, UpdateAlbumUserDto updateAlbumUserDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/albums/{id}/user/{userId}' .replaceAll('{id}', id) @@ -787,6 +826,7 @@ class AlbumsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -801,8 +841,8 @@ class AlbumsApi { /// * [String] userId (required): /// /// * [UpdateAlbumUserDto] updateAlbumUserDto (required): - Future updateAlbumUser(String id, String userId, UpdateAlbumUserDto updateAlbumUserDto,) async { - final response = await updateAlbumUserWithHttpInfo(id, userId, updateAlbumUserDto,); + Future updateAlbumUser(String id, String userId, UpdateAlbumUserDto updateAlbumUserDto, { Future? abortTrigger, }) async { + final response = await updateAlbumUserWithHttpInfo(id, userId, updateAlbumUserDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } diff --git a/mobile/openapi/lib/api/api_keys_api.dart b/mobile/openapi/lib/api/api_keys_api.dart index 3ca85265c4..c26ddc263d 100644 --- a/mobile/openapi/lib/api/api_keys_api.dart +++ b/mobile/openapi/lib/api/api_keys_api.dart @@ -25,7 +25,7 @@ class APIKeysApi { /// Parameters: /// /// * [ApiKeyCreateDto] apiKeyCreateDto (required): - Future createApiKeyWithHttpInfo(ApiKeyCreateDto apiKeyCreateDto,) async { + Future createApiKeyWithHttpInfo(ApiKeyCreateDto apiKeyCreateDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/api-keys'; @@ -47,6 +47,7 @@ class APIKeysApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -57,8 +58,8 @@ class APIKeysApi { /// Parameters: /// /// * [ApiKeyCreateDto] apiKeyCreateDto (required): - Future createApiKey(ApiKeyCreateDto apiKeyCreateDto,) async { - final response = await createApiKeyWithHttpInfo(apiKeyCreateDto,); + Future createApiKey(ApiKeyCreateDto apiKeyCreateDto, { Future? abortTrigger, }) async { + final response = await createApiKeyWithHttpInfo(apiKeyCreateDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -81,7 +82,7 @@ class APIKeysApi { /// Parameters: /// /// * [String] id (required): - Future deleteApiKeyWithHttpInfo(String id,) async { + Future deleteApiKeyWithHttpInfo(String id, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/api-keys/{id}' .replaceAll('{id}', id); @@ -104,6 +105,7 @@ class APIKeysApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -114,8 +116,8 @@ class APIKeysApi { /// Parameters: /// /// * [String] id (required): - Future deleteApiKey(String id,) async { - final response = await deleteApiKeyWithHttpInfo(id,); + Future deleteApiKey(String id, { Future? abortTrigger, }) async { + final response = await deleteApiKeyWithHttpInfo(id, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -130,7 +132,7 @@ class APIKeysApi { /// Parameters: /// /// * [String] id (required): - Future getApiKeyWithHttpInfo(String id,) async { + Future getApiKeyWithHttpInfo(String id, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/api-keys/{id}' .replaceAll('{id}', id); @@ -153,6 +155,7 @@ class APIKeysApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -163,8 +166,8 @@ class APIKeysApi { /// Parameters: /// /// * [String] id (required): - Future getApiKey(String id,) async { - final response = await getApiKeyWithHttpInfo(id,); + Future getApiKey(String id, { Future? abortTrigger, }) async { + final response = await getApiKeyWithHttpInfo(id, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -183,7 +186,7 @@ class APIKeysApi { /// Retrieve all API keys of the current user. /// /// Note: This method returns the HTTP [Response]. - Future getApiKeysWithHttpInfo() async { + Future getApiKeysWithHttpInfo({ Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/api-keys'; @@ -205,14 +208,15 @@ class APIKeysApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } /// List all API keys /// /// Retrieve all API keys of the current user. - Future?> getApiKeys() async { - final response = await getApiKeysWithHttpInfo(); + Future?> getApiKeys({ Future? abortTrigger, }) async { + final response = await getApiKeysWithHttpInfo(abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -234,7 +238,7 @@ class APIKeysApi { /// Retrieve the API key that is used to access this endpoint. /// /// Note: This method returns the HTTP [Response]. - Future getMyApiKeyWithHttpInfo() async { + Future getMyApiKeyWithHttpInfo({ Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/api-keys/me'; @@ -256,14 +260,15 @@ class APIKeysApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } /// Retrieve the current API key /// /// Retrieve the API key that is used to access this endpoint. - Future getMyApiKey() async { - final response = await getMyApiKeyWithHttpInfo(); + Future getMyApiKey({ Future? abortTrigger, }) async { + final response = await getMyApiKeyWithHttpInfo(abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -288,7 +293,7 @@ class APIKeysApi { /// * [String] id (required): /// /// * [ApiKeyUpdateDto] apiKeyUpdateDto (required): - Future updateApiKeyWithHttpInfo(String id, ApiKeyUpdateDto apiKeyUpdateDto,) async { + Future updateApiKeyWithHttpInfo(String id, ApiKeyUpdateDto apiKeyUpdateDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/api-keys/{id}' .replaceAll('{id}', id); @@ -311,6 +316,7 @@ class APIKeysApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -323,8 +329,8 @@ class APIKeysApi { /// * [String] id (required): /// /// * [ApiKeyUpdateDto] apiKeyUpdateDto (required): - Future updateApiKey(String id, ApiKeyUpdateDto apiKeyUpdateDto,) async { - final response = await updateApiKeyWithHttpInfo(id, apiKeyUpdateDto,); + Future updateApiKey(String id, ApiKeyUpdateDto apiKeyUpdateDto, { Future? abortTrigger, }) async { + final response = await updateApiKeyWithHttpInfo(id, apiKeyUpdateDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } diff --git a/mobile/openapi/lib/api/assets_api.dart b/mobile/openapi/lib/api/assets_api.dart index 691c57cd3e..61d3f599cb 100644 --- a/mobile/openapi/lib/api/assets_api.dart +++ b/mobile/openapi/lib/api/assets_api.dart @@ -25,7 +25,7 @@ class AssetsApi { /// Parameters: /// /// * [AssetBulkUploadCheckDto] assetBulkUploadCheckDto (required): - Future checkBulkUploadWithHttpInfo(AssetBulkUploadCheckDto assetBulkUploadCheckDto,) async { + Future checkBulkUploadWithHttpInfo(AssetBulkUploadCheckDto assetBulkUploadCheckDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/assets/bulk-upload-check'; @@ -47,6 +47,7 @@ class AssetsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -57,8 +58,8 @@ class AssetsApi { /// Parameters: /// /// * [AssetBulkUploadCheckDto] assetBulkUploadCheckDto (required): - Future checkBulkUpload(AssetBulkUploadCheckDto assetBulkUploadCheckDto,) async { - final response = await checkBulkUploadWithHttpInfo(assetBulkUploadCheckDto,); + Future checkBulkUpload(AssetBulkUploadCheckDto assetBulkUploadCheckDto, { Future? abortTrigger, }) async { + final response = await checkBulkUploadWithHttpInfo(assetBulkUploadCheckDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -81,7 +82,7 @@ class AssetsApi { /// Parameters: /// /// * [AssetCopyDto] assetCopyDto (required): - Future copyAssetWithHttpInfo(AssetCopyDto assetCopyDto,) async { + Future copyAssetWithHttpInfo(AssetCopyDto assetCopyDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/assets/copy'; @@ -103,6 +104,7 @@ class AssetsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -113,8 +115,8 @@ class AssetsApi { /// Parameters: /// /// * [AssetCopyDto] assetCopyDto (required): - Future copyAsset(AssetCopyDto assetCopyDto,) async { - final response = await copyAssetWithHttpInfo(assetCopyDto,); + Future copyAsset(AssetCopyDto assetCopyDto, { Future? abortTrigger, }) async { + final response = await copyAssetWithHttpInfo(assetCopyDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -133,7 +135,7 @@ class AssetsApi { /// /// * [String] key (required): /// Metadata key - Future deleteAssetMetadataWithHttpInfo(String id, String key,) async { + Future deleteAssetMetadataWithHttpInfo(String id, String key, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/assets/{id}/metadata/{key}' .replaceAll('{id}', id) @@ -157,6 +159,7 @@ class AssetsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -171,8 +174,8 @@ class AssetsApi { /// /// * [String] key (required): /// Metadata key - Future deleteAssetMetadata(String id, String key,) async { - final response = await deleteAssetMetadataWithHttpInfo(id, key,); + Future deleteAssetMetadata(String id, String key, { Future? abortTrigger, }) async { + final response = await deleteAssetMetadataWithHttpInfo(id, key, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -187,7 +190,7 @@ class AssetsApi { /// Parameters: /// /// * [AssetBulkDeleteDto] assetBulkDeleteDto (required): - Future deleteAssetsWithHttpInfo(AssetBulkDeleteDto assetBulkDeleteDto,) async { + Future deleteAssetsWithHttpInfo(AssetBulkDeleteDto assetBulkDeleteDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/assets'; @@ -209,6 +212,7 @@ class AssetsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -219,8 +223,8 @@ class AssetsApi { /// Parameters: /// /// * [AssetBulkDeleteDto] assetBulkDeleteDto (required): - Future deleteAssets(AssetBulkDeleteDto assetBulkDeleteDto,) async { - final response = await deleteAssetsWithHttpInfo(assetBulkDeleteDto,); + Future deleteAssets(AssetBulkDeleteDto assetBulkDeleteDto, { Future? abortTrigger, }) async { + final response = await deleteAssetsWithHttpInfo(assetBulkDeleteDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -235,7 +239,7 @@ class AssetsApi { /// Parameters: /// /// * [AssetMetadataBulkDeleteDto] assetMetadataBulkDeleteDto (required): - Future deleteBulkAssetMetadataWithHttpInfo(AssetMetadataBulkDeleteDto assetMetadataBulkDeleteDto,) async { + Future deleteBulkAssetMetadataWithHttpInfo(AssetMetadataBulkDeleteDto assetMetadataBulkDeleteDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/assets/metadata'; @@ -257,6 +261,7 @@ class AssetsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -267,8 +272,8 @@ class AssetsApi { /// Parameters: /// /// * [AssetMetadataBulkDeleteDto] assetMetadataBulkDeleteDto (required): - Future deleteBulkAssetMetadata(AssetMetadataBulkDeleteDto assetMetadataBulkDeleteDto,) async { - final response = await deleteBulkAssetMetadataWithHttpInfo(assetMetadataBulkDeleteDto,); + Future deleteBulkAssetMetadata(AssetMetadataBulkDeleteDto assetMetadataBulkDeleteDto, { Future? abortTrigger, }) async { + final response = await deleteBulkAssetMetadataWithHttpInfo(assetMetadataBulkDeleteDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -290,7 +295,7 @@ class AssetsApi { /// * [String] key: /// /// * [String] slug: - Future downloadAssetWithHttpInfo(String id, { bool? edited, String? key, String? slug, }) async { + Future downloadAssetWithHttpInfo(String id, { bool? edited, String? key, String? slug, Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/assets/{id}/original' .replaceAll('{id}', id); @@ -323,6 +328,7 @@ class AssetsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -340,8 +346,8 @@ class AssetsApi { /// * [String] key: /// /// * [String] slug: - Future downloadAsset(String id, { bool? edited, String? key, String? slug, }) async { - final response = await downloadAssetWithHttpInfo(id, edited: edited, key: key, slug: slug, ); + Future downloadAsset(String id, { bool? edited, String? key, String? slug, Future? abortTrigger, }) async { + final response = await downloadAssetWithHttpInfo(id, edited: edited, key: key, slug: slug, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -366,7 +372,7 @@ class AssetsApi { /// * [String] id (required): /// /// * [AssetEditsCreateDto] assetEditsCreateDto (required): - Future editAssetWithHttpInfo(String id, AssetEditsCreateDto assetEditsCreateDto,) async { + Future editAssetWithHttpInfo(String id, AssetEditsCreateDto assetEditsCreateDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/assets/{id}/edits' .replaceAll('{id}', id); @@ -389,6 +395,7 @@ class AssetsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -401,8 +408,8 @@ class AssetsApi { /// * [String] id (required): /// /// * [AssetEditsCreateDto] assetEditsCreateDto (required): - Future editAsset(String id, AssetEditsCreateDto assetEditsCreateDto,) async { - final response = await editAssetWithHttpInfo(id, assetEditsCreateDto,); + Future editAsset(String id, AssetEditsCreateDto assetEditsCreateDto, { Future? abortTrigger, }) async { + final response = await editAssetWithHttpInfo(id, assetEditsCreateDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -416,6 +423,76 @@ class AssetsApi { return null; } + /// End HLS streaming session + /// + /// Releases server resources for the streaming session. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [String] sessionId (required): + /// + /// * [String] key: + /// + /// * [String] slug: + Future endSessionWithHttpInfo(String id, String sessionId, { String? key, String? slug, Future? abortTrigger, }) async { + // ignore: prefer_const_declarations + final apiPath = r'/assets/{id}/video/stream/{sessionId}' + .replaceAll('{id}', id) + .replaceAll('{sessionId}', sessionId); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + if (key != null) { + queryParams.addAll(_queryParams('', 'key', key)); + } + if (slug != null) { + queryParams.addAll(_queryParams('', 'slug', slug)); + } + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'DELETE', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, + ); + } + + /// End HLS streaming session + /// + /// Releases server resources for the streaming session. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [String] sessionId (required): + /// + /// * [String] key: + /// + /// * [String] slug: + Future endSession(String id, String sessionId, { String? key, String? slug, Future? abortTrigger, }) async { + final response = await endSessionWithHttpInfo(id, sessionId, key: key, slug: slug, abortTrigger: abortTrigger,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } + /// Retrieve edits for an existing asset /// /// Retrieve a series of edit actions (crop, rotate, mirror) associated with the specified asset. @@ -425,7 +502,7 @@ class AssetsApi { /// Parameters: /// /// * [String] id (required): - Future getAssetEditsWithHttpInfo(String id,) async { + Future getAssetEditsWithHttpInfo(String id, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/assets/{id}/edits' .replaceAll('{id}', id); @@ -448,6 +525,7 @@ class AssetsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -458,8 +536,8 @@ class AssetsApi { /// Parameters: /// /// * [String] id (required): - Future getAssetEdits(String id,) async { - final response = await getAssetEditsWithHttpInfo(id,); + Future getAssetEdits(String id, { Future? abortTrigger, }) async { + final response = await getAssetEditsWithHttpInfo(id, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -486,7 +564,7 @@ class AssetsApi { /// * [String] key: /// /// * [String] slug: - Future getAssetInfoWithHttpInfo(String id, { String? key, String? slug, }) async { + Future getAssetInfoWithHttpInfo(String id, { String? key, String? slug, Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/assets/{id}' .replaceAll('{id}', id); @@ -516,6 +594,7 @@ class AssetsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -530,8 +609,8 @@ class AssetsApi { /// * [String] key: /// /// * [String] slug: - Future getAssetInfo(String id, { String? key, String? slug, }) async { - final response = await getAssetInfoWithHttpInfo(id, key: key, slug: slug, ); + Future getAssetInfo(String id, { String? key, String? slug, Future? abortTrigger, }) async { + final response = await getAssetInfoWithHttpInfo(id, key: key, slug: slug, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -554,7 +633,7 @@ class AssetsApi { /// Parameters: /// /// * [String] id (required): - Future getAssetMetadataWithHttpInfo(String id,) async { + Future getAssetMetadataWithHttpInfo(String id, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/assets/{id}/metadata' .replaceAll('{id}', id); @@ -577,6 +656,7 @@ class AssetsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -587,8 +667,8 @@ class AssetsApi { /// Parameters: /// /// * [String] id (required): - Future?> getAssetMetadata(String id,) async { - final response = await getAssetMetadataWithHttpInfo(id,); + Future?> getAssetMetadata(String id, { Future? abortTrigger, }) async { + final response = await getAssetMetadataWithHttpInfo(id, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -618,7 +698,7 @@ class AssetsApi { /// /// * [String] key (required): /// Metadata key - Future getAssetMetadataByKeyWithHttpInfo(String id, String key,) async { + Future getAssetMetadataByKeyWithHttpInfo(String id, String key, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/assets/{id}/metadata/{key}' .replaceAll('{id}', id) @@ -642,6 +722,7 @@ class AssetsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -656,8 +737,8 @@ class AssetsApi { /// /// * [String] key (required): /// Metadata key - Future getAssetMetadataByKey(String id, String key,) async { - final response = await getAssetMetadataByKeyWithHttpInfo(id, key,); + Future getAssetMetadataByKey(String id, String key, { Future? abortTrigger, }) async { + final response = await getAssetMetadataByKeyWithHttpInfo(id, key, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -680,7 +761,7 @@ class AssetsApi { /// Parameters: /// /// * [String] id (required): - Future getAssetOcrWithHttpInfo(String id,) async { + Future getAssetOcrWithHttpInfo(String id, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/assets/{id}/ocr' .replaceAll('{id}', id); @@ -703,6 +784,7 @@ class AssetsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -713,8 +795,8 @@ class AssetsApi { /// Parameters: /// /// * [String] id (required): - Future?> getAssetOcr(String id,) async { - final response = await getAssetOcrWithHttpInfo(id,); + Future?> getAssetOcr(String id, { Future? abortTrigger, }) async { + final response = await getAssetOcrWithHttpInfo(id, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -746,7 +828,7 @@ class AssetsApi { /// Filter by trash status /// /// * [AssetVisibility] visibility: - Future getAssetStatisticsWithHttpInfo({ bool? isFavorite, bool? isTrashed, AssetVisibility? visibility, }) async { + Future getAssetStatisticsWithHttpInfo({ bool? isFavorite, bool? isTrashed, AssetVisibility? visibility, Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/assets/statistics'; @@ -778,6 +860,7 @@ class AssetsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -794,8 +877,8 @@ class AssetsApi { /// Filter by trash status /// /// * [AssetVisibility] visibility: - Future getAssetStatistics({ bool? isFavorite, bool? isTrashed, AssetVisibility? visibility, }) async { - final response = await getAssetStatisticsWithHttpInfo( isFavorite: isFavorite, isTrashed: isTrashed, visibility: visibility, ); + Future getAssetStatistics({ bool? isFavorite, bool? isTrashed, AssetVisibility? visibility, Future? abortTrigger, }) async { + final response = await getAssetStatisticsWithHttpInfo(isFavorite: isFavorite, isTrashed: isTrashed, visibility: visibility, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -809,6 +892,250 @@ class AssetsApi { return null; } + /// Get HLS main playlist + /// + /// Returns an HLS main playlist with all available variants for the asset. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [String] key: + /// + /// * [String] slug: + Future getMainPlaylistWithHttpInfo(String id, { String? key, String? slug, Future? abortTrigger, }) async { + // ignore: prefer_const_declarations + final apiPath = r'/assets/{id}/video/stream/main.m3u8' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + if (key != null) { + queryParams.addAll(_queryParams('', 'key', key)); + } + if (slug != null) { + queryParams.addAll(_queryParams('', 'slug', slug)); + } + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, + ); + } + + /// Get HLS main playlist + /// + /// Returns an HLS main playlist with all available variants for the asset. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [String] key: + /// + /// * [String] slug: + Future getMainPlaylist(String id, { String? key, String? slug, Future? abortTrigger, }) async { + final response = await getMainPlaylistWithHttpInfo(id, key: key, slug: slug, abortTrigger: abortTrigger,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'String',) as String; + + } + return null; + } + + /// Get HLS media playlist + /// + /// Returns an HLS media playlist for one variant of the streaming session. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [String] sessionId (required): + /// + /// * [int] variantIndex (required): + /// + /// * [String] key: + /// + /// * [String] slug: + Future getMediaPlaylistWithHttpInfo(String id, String sessionId, int variantIndex, { String? key, String? slug, Future? abortTrigger, }) async { + // ignore: prefer_const_declarations + final apiPath = r'/assets/{id}/video/stream/{sessionId}/{variantIndex}/playlist.m3u8' + .replaceAll('{id}', id) + .replaceAll('{sessionId}', sessionId) + .replaceAll('{variantIndex}', variantIndex.toString()); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + if (key != null) { + queryParams.addAll(_queryParams('', 'key', key)); + } + if (slug != null) { + queryParams.addAll(_queryParams('', 'slug', slug)); + } + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, + ); + } + + /// Get HLS media playlist + /// + /// Returns an HLS media playlist for one variant of the streaming session. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [String] sessionId (required): + /// + /// * [int] variantIndex (required): + /// + /// * [String] key: + /// + /// * [String] slug: + Future getMediaPlaylist(String id, String sessionId, int variantIndex, { String? key, String? slug, Future? abortTrigger, }) async { + final response = await getMediaPlaylistWithHttpInfo(id, sessionId, variantIndex, key: key, slug: slug, abortTrigger: abortTrigger,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'String',) as String; + + } + return null; + } + + /// Get HLS segment or init file + /// + /// Streams an HLS init segment (init.mp4) or media segment (seg_N.m4s). + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] filename (required): + /// + /// * [String] id (required): + /// + /// * [String] sessionId (required): + /// + /// * [int] variantIndex (required): + /// + /// * [String] key: + /// + /// * [String] slug: + Future getSegmentWithHttpInfo(String filename, String id, String sessionId, int variantIndex, { String? key, String? slug, Future? abortTrigger, }) async { + // ignore: prefer_const_declarations + final apiPath = r'/assets/{id}/video/stream/{sessionId}/{variantIndex}/{filename}' + .replaceAll('{filename}', filename) + .replaceAll('{id}', id) + .replaceAll('{sessionId}', sessionId) + .replaceAll('{variantIndex}', variantIndex.toString()); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + if (key != null) { + queryParams.addAll(_queryParams('', 'key', key)); + } + if (slug != null) { + queryParams.addAll(_queryParams('', 'slug', slug)); + } + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, + ); + } + + /// Get HLS segment or init file + /// + /// Streams an HLS init segment (init.mp4) or media segment (seg_N.m4s). + /// + /// Parameters: + /// + /// * [String] filename (required): + /// + /// * [String] id (required): + /// + /// * [String] sessionId (required): + /// + /// * [int] variantIndex (required): + /// + /// * [String] key: + /// + /// * [String] slug: + Future getSegment(String filename, String id, String sessionId, int variantIndex, { String? key, String? slug, Future? abortTrigger, }) async { + final response = await getSegmentWithHttpInfo(filename, id, sessionId, variantIndex, key: key, slug: slug, abortTrigger: abortTrigger,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'MultipartFile',) as MultipartFile; + + } + return null; + } + /// Play asset video /// /// Streams the video file for the specified asset. This endpoint also supports byte range requests. @@ -822,7 +1149,7 @@ class AssetsApi { /// * [String] key: /// /// * [String] slug: - Future playAssetVideoWithHttpInfo(String id, { String? key, String? slug, }) async { + Future playAssetVideoWithHttpInfo(String id, { String? key, String? slug, Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/assets/{id}/video/playback' .replaceAll('{id}', id); @@ -852,6 +1179,7 @@ class AssetsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -866,8 +1194,8 @@ class AssetsApi { /// * [String] key: /// /// * [String] slug: - Future playAssetVideo(String id, { String? key, String? slug, }) async { - final response = await playAssetVideoWithHttpInfo(id, key: key, slug: slug, ); + Future playAssetVideo(String id, { String? key, String? slug, Future? abortTrigger, }) async { + final response = await playAssetVideoWithHttpInfo(id, key: key, slug: slug, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -890,7 +1218,7 @@ class AssetsApi { /// Parameters: /// /// * [String] id (required): - Future removeAssetEditsWithHttpInfo(String id,) async { + Future removeAssetEditsWithHttpInfo(String id, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/assets/{id}/edits' .replaceAll('{id}', id); @@ -913,6 +1241,7 @@ class AssetsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -923,8 +1252,8 @@ class AssetsApi { /// Parameters: /// /// * [String] id (required): - Future removeAssetEdits(String id,) async { - final response = await removeAssetEditsWithHttpInfo(id,); + Future removeAssetEdits(String id, { Future? abortTrigger, }) async { + final response = await removeAssetEditsWithHttpInfo(id, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -939,7 +1268,7 @@ class AssetsApi { /// Parameters: /// /// * [AssetJobsDto] assetJobsDto (required): - Future runAssetJobsWithHttpInfo(AssetJobsDto assetJobsDto,) async { + Future runAssetJobsWithHttpInfo(AssetJobsDto assetJobsDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/assets/jobs'; @@ -961,6 +1290,7 @@ class AssetsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -971,8 +1301,8 @@ class AssetsApi { /// Parameters: /// /// * [AssetJobsDto] assetJobsDto (required): - Future runAssetJobs(AssetJobsDto assetJobsDto,) async { - final response = await runAssetJobsWithHttpInfo(assetJobsDto,); + Future runAssetJobs(AssetJobsDto assetJobsDto, { Future? abortTrigger, }) async { + final response = await runAssetJobsWithHttpInfo(assetJobsDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -989,7 +1319,7 @@ class AssetsApi { /// * [String] id (required): /// /// * [UpdateAssetDto] updateAssetDto (required): - Future updateAssetWithHttpInfo(String id, UpdateAssetDto updateAssetDto,) async { + Future updateAssetWithHttpInfo(String id, UpdateAssetDto updateAssetDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/assets/{id}' .replaceAll('{id}', id); @@ -1012,6 +1342,7 @@ class AssetsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -1024,8 +1355,8 @@ class AssetsApi { /// * [String] id (required): /// /// * [UpdateAssetDto] updateAssetDto (required): - Future updateAsset(String id, UpdateAssetDto updateAssetDto,) async { - final response = await updateAssetWithHttpInfo(id, updateAssetDto,); + Future updateAsset(String id, UpdateAssetDto updateAssetDto, { Future? abortTrigger, }) async { + final response = await updateAssetWithHttpInfo(id, updateAssetDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -1050,7 +1381,7 @@ class AssetsApi { /// * [String] id (required): /// /// * [AssetMetadataUpsertDto] assetMetadataUpsertDto (required): - Future updateAssetMetadataWithHttpInfo(String id, AssetMetadataUpsertDto assetMetadataUpsertDto,) async { + Future updateAssetMetadataWithHttpInfo(String id, AssetMetadataUpsertDto assetMetadataUpsertDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/assets/{id}/metadata' .replaceAll('{id}', id); @@ -1073,6 +1404,7 @@ class AssetsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -1085,8 +1417,8 @@ class AssetsApi { /// * [String] id (required): /// /// * [AssetMetadataUpsertDto] assetMetadataUpsertDto (required): - Future?> updateAssetMetadata(String id, AssetMetadataUpsertDto assetMetadataUpsertDto,) async { - final response = await updateAssetMetadataWithHttpInfo(id, assetMetadataUpsertDto,); + Future?> updateAssetMetadata(String id, AssetMetadataUpsertDto assetMetadataUpsertDto, { Future? abortTrigger, }) async { + final response = await updateAssetMetadataWithHttpInfo(id, assetMetadataUpsertDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -1112,7 +1444,7 @@ class AssetsApi { /// Parameters: /// /// * [AssetBulkUpdateDto] assetBulkUpdateDto (required): - Future updateAssetsWithHttpInfo(AssetBulkUpdateDto assetBulkUpdateDto,) async { + Future updateAssetsWithHttpInfo(AssetBulkUpdateDto assetBulkUpdateDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/assets'; @@ -1134,6 +1466,7 @@ class AssetsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -1144,8 +1477,8 @@ class AssetsApi { /// Parameters: /// /// * [AssetBulkUpdateDto] assetBulkUpdateDto (required): - Future updateAssets(AssetBulkUpdateDto assetBulkUpdateDto,) async { - final response = await updateAssetsWithHttpInfo(assetBulkUpdateDto,); + Future updateAssets(AssetBulkUpdateDto assetBulkUpdateDto, { Future? abortTrigger, }) async { + final response = await updateAssetsWithHttpInfo(assetBulkUpdateDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -1160,7 +1493,7 @@ class AssetsApi { /// Parameters: /// /// * [AssetMetadataBulkUpsertDto] assetMetadataBulkUpsertDto (required): - Future updateBulkAssetMetadataWithHttpInfo(AssetMetadataBulkUpsertDto assetMetadataBulkUpsertDto,) async { + Future updateBulkAssetMetadataWithHttpInfo(AssetMetadataBulkUpsertDto assetMetadataBulkUpsertDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/assets/metadata'; @@ -1182,6 +1515,7 @@ class AssetsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -1192,8 +1526,8 @@ class AssetsApi { /// Parameters: /// /// * [AssetMetadataBulkUpsertDto] assetMetadataBulkUpsertDto (required): - Future?> updateBulkAssetMetadata(AssetMetadataBulkUpsertDto assetMetadataBulkUpsertDto,) async { - final response = await updateBulkAssetMetadataWithHttpInfo(assetMetadataBulkUpsertDto,); + Future?> updateBulkAssetMetadata(AssetMetadataBulkUpsertDto assetMetadataBulkUpsertDto, { Future? abortTrigger, }) async { + final response = await updateBulkAssetMetadataWithHttpInfo(assetMetadataBulkUpsertDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -1253,7 +1587,7 @@ class AssetsApi { /// Sidecar file data /// /// * [AssetVisibility] visibility: - Future uploadAssetWithHttpInfo(MultipartFile assetData, DateTime fileCreatedAt, DateTime fileModifiedAt, { String? key, String? slug, String? xImmichChecksum, int? duration, String? filename, bool? isFavorite, String? livePhotoVideoId, List? metadata, MultipartFile? sidecarData, AssetVisibility? visibility, }) async { + Future uploadAssetWithHttpInfo(MultipartFile assetData, DateTime fileCreatedAt, DateTime fileModifiedAt, { String? key, String? slug, String? xImmichChecksum, int? duration, String? filename, bool? isFavorite, String? livePhotoVideoId, List? metadata, MultipartFile? sidecarData, AssetVisibility? visibility, Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/assets'; @@ -1333,6 +1667,7 @@ class AssetsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -1377,8 +1712,8 @@ class AssetsApi { /// Sidecar file data /// /// * [AssetVisibility] visibility: - Future uploadAsset(MultipartFile assetData, DateTime fileCreatedAt, DateTime fileModifiedAt, { String? key, String? slug, String? xImmichChecksum, int? duration, String? filename, bool? isFavorite, String? livePhotoVideoId, List? metadata, MultipartFile? sidecarData, AssetVisibility? visibility, }) async { - final response = await uploadAssetWithHttpInfo(assetData, fileCreatedAt, fileModifiedAt, key: key, slug: slug, xImmichChecksum: xImmichChecksum, duration: duration, filename: filename, isFavorite: isFavorite, livePhotoVideoId: livePhotoVideoId, metadata: metadata, sidecarData: sidecarData, visibility: visibility, ); + Future uploadAsset(MultipartFile assetData, DateTime fileCreatedAt, DateTime fileModifiedAt, { String? key, String? slug, String? xImmichChecksum, int? duration, String? filename, bool? isFavorite, String? livePhotoVideoId, List? metadata, MultipartFile? sidecarData, AssetVisibility? visibility, Future? abortTrigger, }) async { + final response = await uploadAssetWithHttpInfo(assetData, fileCreatedAt, fileModifiedAt, key: key, slug: slug, xImmichChecksum: xImmichChecksum, duration: duration, filename: filename, isFavorite: isFavorite, livePhotoVideoId: livePhotoVideoId, metadata: metadata, sidecarData: sidecarData, visibility: visibility, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -1410,7 +1745,7 @@ class AssetsApi { /// * [AssetMediaSize] size: /// /// * [String] slug: - Future viewAssetWithHttpInfo(String id, { bool? edited, String? key, AssetMediaSize? size, String? slug, }) async { + Future viewAssetWithHttpInfo(String id, { bool? edited, String? key, AssetMediaSize? size, String? slug, Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/assets/{id}/thumbnail' .replaceAll('{id}', id); @@ -1446,6 +1781,7 @@ class AssetsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -1465,8 +1801,8 @@ class AssetsApi { /// * [AssetMediaSize] size: /// /// * [String] slug: - Future viewAsset(String id, { bool? edited, String? key, AssetMediaSize? size, String? slug, }) async { - final response = await viewAssetWithHttpInfo(id, edited: edited, key: key, size: size, slug: slug, ); + Future viewAsset(String id, { bool? edited, String? key, AssetMediaSize? size, String? slug, Future? abortTrigger, }) async { + final response = await viewAssetWithHttpInfo(id, edited: edited, key: key, size: size, slug: slug, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } diff --git a/mobile/openapi/lib/api/authentication_admin_api.dart b/mobile/openapi/lib/api/authentication_admin_api.dart index 0a4b91ebc3..2c107891b3 100644 --- a/mobile/openapi/lib/api/authentication_admin_api.dart +++ b/mobile/openapi/lib/api/authentication_admin_api.dart @@ -21,7 +21,7 @@ class AuthenticationAdminApi { /// Unlinks all OAuth accounts associated with user accounts in the system. /// /// Note: This method returns the HTTP [Response]. - Future unlinkAllOAuthAccountsAdminWithHttpInfo() async { + Future unlinkAllOAuthAccountsAdminWithHttpInfo({ Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/admin/auth/unlink-all'; @@ -43,14 +43,15 @@ class AuthenticationAdminApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } /// Unlink all OAuth accounts /// /// Unlinks all OAuth accounts associated with user accounts in the system. - Future unlinkAllOAuthAccountsAdmin() async { - final response = await unlinkAllOAuthAccountsAdminWithHttpInfo(); + Future unlinkAllOAuthAccountsAdmin({ Future? abortTrigger, }) async { + final response = await unlinkAllOAuthAccountsAdminWithHttpInfo(abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } diff --git a/mobile/openapi/lib/api/authentication_api.dart b/mobile/openapi/lib/api/authentication_api.dart index e1219f2c03..8e088d040b 100644 --- a/mobile/openapi/lib/api/authentication_api.dart +++ b/mobile/openapi/lib/api/authentication_api.dart @@ -25,7 +25,7 @@ class AuthenticationApi { /// Parameters: /// /// * [ChangePasswordDto] changePasswordDto (required): - Future changePasswordWithHttpInfo(ChangePasswordDto changePasswordDto,) async { + Future changePasswordWithHttpInfo(ChangePasswordDto changePasswordDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/auth/change-password'; @@ -47,6 +47,7 @@ class AuthenticationApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -57,8 +58,8 @@ class AuthenticationApi { /// Parameters: /// /// * [ChangePasswordDto] changePasswordDto (required): - Future changePassword(ChangePasswordDto changePasswordDto,) async { - final response = await changePasswordWithHttpInfo(changePasswordDto,); + Future changePassword(ChangePasswordDto changePasswordDto, { Future? abortTrigger, }) async { + final response = await changePasswordWithHttpInfo(changePasswordDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -81,7 +82,7 @@ class AuthenticationApi { /// Parameters: /// /// * [PinCodeChangeDto] pinCodeChangeDto (required): - Future changePinCodeWithHttpInfo(PinCodeChangeDto pinCodeChangeDto,) async { + Future changePinCodeWithHttpInfo(PinCodeChangeDto pinCodeChangeDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/auth/pin-code'; @@ -103,6 +104,7 @@ class AuthenticationApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -113,8 +115,8 @@ class AuthenticationApi { /// Parameters: /// /// * [PinCodeChangeDto] pinCodeChangeDto (required): - Future changePinCode(PinCodeChangeDto pinCodeChangeDto,) async { - final response = await changePinCodeWithHttpInfo(pinCodeChangeDto,); + Future changePinCode(PinCodeChangeDto pinCodeChangeDto, { Future? abortTrigger, }) async { + final response = await changePinCodeWithHttpInfo(pinCodeChangeDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -129,7 +131,7 @@ class AuthenticationApi { /// Parameters: /// /// * [OAuthCallbackDto] oAuthCallbackDto (required): - Future finishOAuthWithHttpInfo(OAuthCallbackDto oAuthCallbackDto,) async { + Future finishOAuthWithHttpInfo(OAuthCallbackDto oAuthCallbackDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/oauth/callback'; @@ -151,6 +153,7 @@ class AuthenticationApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -161,8 +164,8 @@ class AuthenticationApi { /// Parameters: /// /// * [OAuthCallbackDto] oAuthCallbackDto (required): - Future finishOAuth(OAuthCallbackDto oAuthCallbackDto,) async { - final response = await finishOAuthWithHttpInfo(oAuthCallbackDto,); + Future finishOAuth(OAuthCallbackDto oAuthCallbackDto, { Future? abortTrigger, }) async { + final response = await finishOAuthWithHttpInfo(oAuthCallbackDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -181,7 +184,7 @@ class AuthenticationApi { /// Get information about the current session, including whether the user has a password, and if the session can access locked assets. /// /// Note: This method returns the HTTP [Response]. - Future getAuthStatusWithHttpInfo() async { + Future getAuthStatusWithHttpInfo({ Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/auth/status'; @@ -203,14 +206,15 @@ class AuthenticationApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } /// Retrieve auth status /// /// Get information about the current session, including whether the user has a password, and if the session can access locked assets. - Future getAuthStatus() async { - final response = await getAuthStatusWithHttpInfo(); + Future getAuthStatus({ Future? abortTrigger, }) async { + final response = await getAuthStatusWithHttpInfo(abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -233,7 +237,7 @@ class AuthenticationApi { /// Parameters: /// /// * [OAuthCallbackDto] oAuthCallbackDto (required): - Future linkOAuthAccountWithHttpInfo(OAuthCallbackDto oAuthCallbackDto,) async { + Future linkOAuthAccountWithHttpInfo(OAuthCallbackDto oAuthCallbackDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/oauth/link'; @@ -255,6 +259,7 @@ class AuthenticationApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -265,8 +270,8 @@ class AuthenticationApi { /// Parameters: /// /// * [OAuthCallbackDto] oAuthCallbackDto (required): - Future linkOAuthAccount(OAuthCallbackDto oAuthCallbackDto,) async { - final response = await linkOAuthAccountWithHttpInfo(oAuthCallbackDto,); + Future linkOAuthAccount(OAuthCallbackDto oAuthCallbackDto, { Future? abortTrigger, }) async { + final response = await linkOAuthAccountWithHttpInfo(oAuthCallbackDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -285,7 +290,7 @@ class AuthenticationApi { /// Remove elevated access to locked assets from the current session. /// /// Note: This method returns the HTTP [Response]. - Future lockAuthSessionWithHttpInfo() async { + Future lockAuthSessionWithHttpInfo({ Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/auth/session/lock'; @@ -307,14 +312,15 @@ class AuthenticationApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } /// Lock auth session /// /// Remove elevated access to locked assets from the current session. - Future lockAuthSession() async { - final response = await lockAuthSessionWithHttpInfo(); + Future lockAuthSession({ Future? abortTrigger, }) async { + final response = await lockAuthSessionWithHttpInfo(abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -329,7 +335,7 @@ class AuthenticationApi { /// Parameters: /// /// * [LoginCredentialDto] loginCredentialDto (required): - Future loginWithHttpInfo(LoginCredentialDto loginCredentialDto,) async { + Future loginWithHttpInfo(LoginCredentialDto loginCredentialDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/auth/login'; @@ -351,6 +357,7 @@ class AuthenticationApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -361,8 +368,8 @@ class AuthenticationApi { /// Parameters: /// /// * [LoginCredentialDto] loginCredentialDto (required): - Future login(LoginCredentialDto loginCredentialDto,) async { - final response = await loginWithHttpInfo(loginCredentialDto,); + Future login(LoginCredentialDto loginCredentialDto, { Future? abortTrigger, }) async { + final response = await loginWithHttpInfo(loginCredentialDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -381,7 +388,7 @@ class AuthenticationApi { /// Logout the current user and invalidate the session token. /// /// Note: This method returns the HTTP [Response]. - Future logoutWithHttpInfo() async { + Future logoutWithHttpInfo({ Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/auth/logout'; @@ -403,14 +410,15 @@ class AuthenticationApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } /// Logout /// /// Logout the current user and invalidate the session token. - Future logout() async { - final response = await logoutWithHttpInfo(); + Future logout({ Future? abortTrigger, }) async { + final response = await logoutWithHttpInfo(abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -434,7 +442,7 @@ class AuthenticationApi { /// /// * [String] logoutToken (required): /// OAuth logout token - Future logoutOAuthWithHttpInfo(String logoutToken,) async { + Future logoutOAuthWithHttpInfo(String logoutToken, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/oauth/backchannel-logout'; @@ -459,6 +467,7 @@ class AuthenticationApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -470,8 +479,8 @@ class AuthenticationApi { /// /// * [String] logoutToken (required): /// OAuth logout token - Future logoutOAuth(String logoutToken,) async { - final response = await logoutOAuthWithHttpInfo(logoutToken,); + Future logoutOAuth(String logoutToken, { Future? abortTrigger, }) async { + final response = await logoutOAuthWithHttpInfo(logoutToken, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -482,7 +491,7 @@ class AuthenticationApi { /// Requests to this URL are automatically forwarded to the mobile app, and is used in some cases for OAuth redirecting. /// /// Note: This method returns the HTTP [Response]. - Future redirectOAuthToMobileWithHttpInfo() async { + Future redirectOAuthToMobileWithHttpInfo({ Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/oauth/mobile-redirect'; @@ -504,14 +513,15 @@ class AuthenticationApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } /// Redirect OAuth to mobile /// /// Requests to this URL are automatically forwarded to the mobile app, and is used in some cases for OAuth redirecting. - Future redirectOAuthToMobile() async { - final response = await redirectOAuthToMobileWithHttpInfo(); + Future redirectOAuthToMobile({ Future? abortTrigger, }) async { + final response = await redirectOAuthToMobileWithHttpInfo(abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -526,7 +536,7 @@ class AuthenticationApi { /// Parameters: /// /// * [PinCodeResetDto] pinCodeResetDto (required): - Future resetPinCodeWithHttpInfo(PinCodeResetDto pinCodeResetDto,) async { + Future resetPinCodeWithHttpInfo(PinCodeResetDto pinCodeResetDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/auth/pin-code'; @@ -548,6 +558,7 @@ class AuthenticationApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -558,8 +569,8 @@ class AuthenticationApi { /// Parameters: /// /// * [PinCodeResetDto] pinCodeResetDto (required): - Future resetPinCode(PinCodeResetDto pinCodeResetDto,) async { - final response = await resetPinCodeWithHttpInfo(pinCodeResetDto,); + Future resetPinCode(PinCodeResetDto pinCodeResetDto, { Future? abortTrigger, }) async { + final response = await resetPinCodeWithHttpInfo(pinCodeResetDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -574,7 +585,7 @@ class AuthenticationApi { /// Parameters: /// /// * [PinCodeSetupDto] pinCodeSetupDto (required): - Future setupPinCodeWithHttpInfo(PinCodeSetupDto pinCodeSetupDto,) async { + Future setupPinCodeWithHttpInfo(PinCodeSetupDto pinCodeSetupDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/auth/pin-code'; @@ -596,6 +607,7 @@ class AuthenticationApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -606,8 +618,8 @@ class AuthenticationApi { /// Parameters: /// /// * [PinCodeSetupDto] pinCodeSetupDto (required): - Future setupPinCode(PinCodeSetupDto pinCodeSetupDto,) async { - final response = await setupPinCodeWithHttpInfo(pinCodeSetupDto,); + Future setupPinCode(PinCodeSetupDto pinCodeSetupDto, { Future? abortTrigger, }) async { + final response = await setupPinCodeWithHttpInfo(pinCodeSetupDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -622,7 +634,7 @@ class AuthenticationApi { /// Parameters: /// /// * [SignUpDto] signUpDto (required): - Future signUpAdminWithHttpInfo(SignUpDto signUpDto,) async { + Future signUpAdminWithHttpInfo(SignUpDto signUpDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/auth/admin-sign-up'; @@ -644,6 +656,7 @@ class AuthenticationApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -654,8 +667,8 @@ class AuthenticationApi { /// Parameters: /// /// * [SignUpDto] signUpDto (required): - Future signUpAdmin(SignUpDto signUpDto,) async { - final response = await signUpAdminWithHttpInfo(signUpDto,); + Future signUpAdmin(SignUpDto signUpDto, { Future? abortTrigger, }) async { + final response = await signUpAdminWithHttpInfo(signUpDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -678,7 +691,7 @@ class AuthenticationApi { /// Parameters: /// /// * [OAuthConfigDto] oAuthConfigDto (required): - Future startOAuthWithHttpInfo(OAuthConfigDto oAuthConfigDto,) async { + Future startOAuthWithHttpInfo(OAuthConfigDto oAuthConfigDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/oauth/authorize'; @@ -700,6 +713,7 @@ class AuthenticationApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -710,8 +724,8 @@ class AuthenticationApi { /// Parameters: /// /// * [OAuthConfigDto] oAuthConfigDto (required): - Future startOAuth(OAuthConfigDto oAuthConfigDto,) async { - final response = await startOAuthWithHttpInfo(oAuthConfigDto,); + Future startOAuth(OAuthConfigDto oAuthConfigDto, { Future? abortTrigger, }) async { + final response = await startOAuthWithHttpInfo(oAuthConfigDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -730,7 +744,7 @@ class AuthenticationApi { /// Unlink the OAuth account from the authenticated user. /// /// Note: This method returns the HTTP [Response]. - Future unlinkOAuthAccountWithHttpInfo() async { + Future unlinkOAuthAccountWithHttpInfo({ Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/oauth/unlink'; @@ -752,14 +766,15 @@ class AuthenticationApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } /// Unlink OAuth account /// /// Unlink the OAuth account from the authenticated user. - Future unlinkOAuthAccount() async { - final response = await unlinkOAuthAccountWithHttpInfo(); + Future unlinkOAuthAccount({ Future? abortTrigger, }) async { + final response = await unlinkOAuthAccountWithHttpInfo(abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -782,7 +797,7 @@ class AuthenticationApi { /// Parameters: /// /// * [SessionUnlockDto] sessionUnlockDto (required): - Future unlockAuthSessionWithHttpInfo(SessionUnlockDto sessionUnlockDto,) async { + Future unlockAuthSessionWithHttpInfo(SessionUnlockDto sessionUnlockDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/auth/session/unlock'; @@ -804,6 +819,7 @@ class AuthenticationApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -814,8 +830,8 @@ class AuthenticationApi { /// Parameters: /// /// * [SessionUnlockDto] sessionUnlockDto (required): - Future unlockAuthSession(SessionUnlockDto sessionUnlockDto,) async { - final response = await unlockAuthSessionWithHttpInfo(sessionUnlockDto,); + Future unlockAuthSession(SessionUnlockDto sessionUnlockDto, { Future? abortTrigger, }) async { + final response = await unlockAuthSessionWithHttpInfo(sessionUnlockDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -826,7 +842,7 @@ class AuthenticationApi { /// Validate the current authorization method is still valid. /// /// Note: This method returns the HTTP [Response]. - Future validateAccessTokenWithHttpInfo() async { + Future validateAccessTokenWithHttpInfo({ Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/auth/validateToken'; @@ -848,14 +864,15 @@ class AuthenticationApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } /// Validate access token /// /// Validate the current authorization method is still valid. - Future validateAccessToken() async { - final response = await validateAccessTokenWithHttpInfo(); + Future validateAccessToken({ Future? abortTrigger, }) async { + final response = await validateAccessTokenWithHttpInfo(abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } diff --git a/mobile/openapi/lib/api/database_backups_admin_api.dart b/mobile/openapi/lib/api/database_backups_admin_api.dart index 768185db1e..ba393833b5 100644 --- a/mobile/openapi/lib/api/database_backups_admin_api.dart +++ b/mobile/openapi/lib/api/database_backups_admin_api.dart @@ -25,7 +25,7 @@ class DatabaseBackupsAdminApi { /// Parameters: /// /// * [DatabaseBackupDeleteDto] databaseBackupDeleteDto (required): - Future deleteDatabaseBackupWithHttpInfo(DatabaseBackupDeleteDto databaseBackupDeleteDto,) async { + Future deleteDatabaseBackupWithHttpInfo(DatabaseBackupDeleteDto databaseBackupDeleteDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/admin/database-backups'; @@ -47,6 +47,7 @@ class DatabaseBackupsAdminApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -57,8 +58,8 @@ class DatabaseBackupsAdminApi { /// Parameters: /// /// * [DatabaseBackupDeleteDto] databaseBackupDeleteDto (required): - Future deleteDatabaseBackup(DatabaseBackupDeleteDto databaseBackupDeleteDto,) async { - final response = await deleteDatabaseBackupWithHttpInfo(databaseBackupDeleteDto,); + Future deleteDatabaseBackup(DatabaseBackupDeleteDto databaseBackupDeleteDto, { Future? abortTrigger, }) async { + final response = await deleteDatabaseBackupWithHttpInfo(databaseBackupDeleteDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -73,7 +74,7 @@ class DatabaseBackupsAdminApi { /// Parameters: /// /// * [String] filename (required): - Future downloadDatabaseBackupWithHttpInfo(String filename,) async { + Future downloadDatabaseBackupWithHttpInfo(String filename, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/admin/database-backups/{filename}' .replaceAll('{filename}', filename); @@ -96,6 +97,7 @@ class DatabaseBackupsAdminApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -106,8 +108,8 @@ class DatabaseBackupsAdminApi { /// Parameters: /// /// * [String] filename (required): - Future downloadDatabaseBackup(String filename,) async { - final response = await downloadDatabaseBackupWithHttpInfo(filename,); + Future downloadDatabaseBackup(String filename, { Future? abortTrigger, }) async { + final response = await downloadDatabaseBackupWithHttpInfo(filename, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -126,7 +128,7 @@ class DatabaseBackupsAdminApi { /// Get the list of the successful and failed backups /// /// Note: This method returns the HTTP [Response]. - Future listDatabaseBackupsWithHttpInfo() async { + Future listDatabaseBackupsWithHttpInfo({ Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/admin/database-backups'; @@ -148,14 +150,15 @@ class DatabaseBackupsAdminApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } /// List database backups /// /// Get the list of the successful and failed backups - Future listDatabaseBackups() async { - final response = await listDatabaseBackupsWithHttpInfo(); + Future listDatabaseBackups({ Future? abortTrigger, }) async { + final response = await listDatabaseBackupsWithHttpInfo(abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -174,7 +177,7 @@ class DatabaseBackupsAdminApi { /// Put Immich into maintenance mode to restore a backup (Immich must not be configured) /// /// Note: This method returns the HTTP [Response]. - Future startDatabaseRestoreFlowWithHttpInfo() async { + Future startDatabaseRestoreFlowWithHttpInfo({ Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/admin/database-backups/start-restore'; @@ -196,14 +199,15 @@ class DatabaseBackupsAdminApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } /// Start database backup restore flow /// /// Put Immich into maintenance mode to restore a backup (Immich must not be configured) - Future startDatabaseRestoreFlow() async { - final response = await startDatabaseRestoreFlowWithHttpInfo(); + Future startDatabaseRestoreFlow({ Future? abortTrigger, }) async { + final response = await startDatabaseRestoreFlowWithHttpInfo(abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -219,7 +223,7 @@ class DatabaseBackupsAdminApi { /// /// * [MultipartFile] file: /// Database backup file - Future uploadDatabaseBackupWithHttpInfo({ MultipartFile? file, }) async { + Future uploadDatabaseBackupWithHttpInfo({ MultipartFile? file, Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/admin/database-backups/upload'; @@ -251,6 +255,7 @@ class DatabaseBackupsAdminApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -262,8 +267,8 @@ class DatabaseBackupsAdminApi { /// /// * [MultipartFile] file: /// Database backup file - Future uploadDatabaseBackup({ MultipartFile? file, }) async { - final response = await uploadDatabaseBackupWithHttpInfo( file: file, ); + Future uploadDatabaseBackup({ MultipartFile? file, Future? abortTrigger, }) async { + final response = await uploadDatabaseBackupWithHttpInfo(file: file, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } diff --git a/mobile/openapi/lib/api/deprecated_api.dart b/mobile/openapi/lib/api/deprecated_api.dart index a437cd5837..a8e2deab44 100644 --- a/mobile/openapi/lib/api/deprecated_api.dart +++ b/mobile/openapi/lib/api/deprecated_api.dart @@ -25,7 +25,7 @@ class DeprecatedApi { /// Parameters: /// /// * [String] id (required): - Future createPartnerDeprecatedWithHttpInfo(String id,) async { + Future createPartnerDeprecatedWithHttpInfo(String id, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/partners/{id}' .replaceAll('{id}', id); @@ -48,6 +48,7 @@ class DeprecatedApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -58,8 +59,8 @@ class DeprecatedApi { /// Parameters: /// /// * [String] id (required): - Future createPartnerDeprecated(String id,) async { - final response = await createPartnerDeprecatedWithHttpInfo(id,); + Future createPartnerDeprecated(String id, { Future? abortTrigger, }) async { + final response = await createPartnerDeprecatedWithHttpInfo(id, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -78,7 +79,7 @@ class DeprecatedApi { /// Retrieve the counts of the current queue, as well as the current status. /// /// Note: This method returns the HTTP [Response]. - Future getQueuesLegacyWithHttpInfo() async { + Future getQueuesLegacyWithHttpInfo({ Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/jobs'; @@ -100,14 +101,15 @@ class DeprecatedApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } /// Retrieve queue counts and status /// /// Retrieve the counts of the current queue, as well as the current status. - Future getQueuesLegacy() async { - final response = await getQueuesLegacyWithHttpInfo(); + Future getQueuesLegacy({ Future? abortTrigger, }) async { + final response = await getQueuesLegacyWithHttpInfo(abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -132,7 +134,7 @@ class DeprecatedApi { /// * [QueueName] name (required): /// /// * [QueueCommandDto] queueCommandDto (required): - Future runQueueCommandLegacyWithHttpInfo(QueueName name, QueueCommandDto queueCommandDto,) async { + Future runQueueCommandLegacyWithHttpInfo(QueueName name, QueueCommandDto queueCommandDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/jobs/{name}' .replaceAll('{name}', name.toString()); @@ -155,6 +157,7 @@ class DeprecatedApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -167,8 +170,8 @@ class DeprecatedApi { /// * [QueueName] name (required): /// /// * [QueueCommandDto] queueCommandDto (required): - Future runQueueCommandLegacy(QueueName name, QueueCommandDto queueCommandDto,) async { - final response = await runQueueCommandLegacyWithHttpInfo(name, queueCommandDto,); + Future runQueueCommandLegacy(QueueName name, QueueCommandDto queueCommandDto, { Future? abortTrigger, }) async { + final response = await runQueueCommandLegacyWithHttpInfo(name, queueCommandDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } diff --git a/mobile/openapi/lib/api/download_api.dart b/mobile/openapi/lib/api/download_api.dart index 4d0c5c8165..ac26259277 100644 --- a/mobile/openapi/lib/api/download_api.dart +++ b/mobile/openapi/lib/api/download_api.dart @@ -29,7 +29,7 @@ class DownloadApi { /// * [String] key: /// /// * [String] slug: - Future downloadArchiveWithHttpInfo(DownloadArchiveDto downloadArchiveDto, { String? key, String? slug, }) async { + Future downloadArchiveWithHttpInfo(DownloadArchiveDto downloadArchiveDto, { String? key, String? slug, Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/download/archive'; @@ -58,6 +58,7 @@ class DownloadApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -72,8 +73,8 @@ class DownloadApi { /// * [String] key: /// /// * [String] slug: - Future downloadArchive(DownloadArchiveDto downloadArchiveDto, { String? key, String? slug, }) async { - final response = await downloadArchiveWithHttpInfo(downloadArchiveDto, key: key, slug: slug, ); + Future downloadArchive(DownloadArchiveDto downloadArchiveDto, { String? key, String? slug, Future? abortTrigger, }) async { + final response = await downloadArchiveWithHttpInfo(downloadArchiveDto, key: key, slug: slug, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -100,7 +101,7 @@ class DownloadApi { /// * [String] key: /// /// * [String] slug: - Future getDownloadInfoWithHttpInfo(DownloadInfoDto downloadInfoDto, { String? key, String? slug, }) async { + Future getDownloadInfoWithHttpInfo(DownloadInfoDto downloadInfoDto, { String? key, String? slug, Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/download/info'; @@ -129,6 +130,7 @@ class DownloadApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -143,8 +145,8 @@ class DownloadApi { /// * [String] key: /// /// * [String] slug: - Future getDownloadInfo(DownloadInfoDto downloadInfoDto, { String? key, String? slug, }) async { - final response = await getDownloadInfoWithHttpInfo(downloadInfoDto, key: key, slug: slug, ); + Future getDownloadInfo(DownloadInfoDto downloadInfoDto, { String? key, String? slug, Future? abortTrigger, }) async { + final response = await getDownloadInfoWithHttpInfo(downloadInfoDto, key: key, slug: slug, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } diff --git a/mobile/openapi/lib/api/duplicates_api.dart b/mobile/openapi/lib/api/duplicates_api.dart index e873537592..357947b889 100644 --- a/mobile/openapi/lib/api/duplicates_api.dart +++ b/mobile/openapi/lib/api/duplicates_api.dart @@ -16,16 +16,16 @@ class DuplicatesApi { final ApiClient apiClient; - /// Delete a duplicate + /// Dismiss a duplicate group /// - /// Delete a single duplicate asset specified by its ID. + /// Dismiss a duplicate group by its ID, unlinking all assets in the group without deleting them. /// /// Note: This method returns the HTTP [Response]. /// /// Parameters: /// /// * [String] id (required): - Future deleteDuplicateWithHttpInfo(String id,) async { + Future deleteDuplicateWithHttpInfo(String id, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/duplicates/{id}' .replaceAll('{id}', id); @@ -48,18 +48,19 @@ class DuplicatesApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } - /// Delete a duplicate + /// Dismiss a duplicate group /// - /// Delete a single duplicate asset specified by its ID. + /// Dismiss a duplicate group by its ID, unlinking all assets in the group without deleting them. /// /// Parameters: /// /// * [String] id (required): - Future deleteDuplicate(String id,) async { - final response = await deleteDuplicateWithHttpInfo(id,); + Future deleteDuplicate(String id, { Future? abortTrigger, }) async { + final response = await deleteDuplicateWithHttpInfo(id, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -74,7 +75,7 @@ class DuplicatesApi { /// Parameters: /// /// * [BulkIdsDto] bulkIdsDto (required): - Future deleteDuplicatesWithHttpInfo(BulkIdsDto bulkIdsDto,) async { + Future deleteDuplicatesWithHttpInfo(BulkIdsDto bulkIdsDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/duplicates'; @@ -96,6 +97,7 @@ class DuplicatesApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -106,8 +108,8 @@ class DuplicatesApi { /// Parameters: /// /// * [BulkIdsDto] bulkIdsDto (required): - Future deleteDuplicates(BulkIdsDto bulkIdsDto,) async { - final response = await deleteDuplicatesWithHttpInfo(bulkIdsDto,); + Future deleteDuplicates(BulkIdsDto bulkIdsDto, { Future? abortTrigger, }) async { + final response = await deleteDuplicatesWithHttpInfo(bulkIdsDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -118,7 +120,7 @@ class DuplicatesApi { /// Retrieve a list of duplicate assets available to the authenticated user. /// /// Note: This method returns the HTTP [Response]. - Future getAssetDuplicatesWithHttpInfo() async { + Future getAssetDuplicatesWithHttpInfo({ Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/duplicates'; @@ -140,14 +142,15 @@ class DuplicatesApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } /// Retrieve duplicates /// /// Retrieve a list of duplicate assets available to the authenticated user. - Future?> getAssetDuplicates() async { - final response = await getAssetDuplicatesWithHttpInfo(); + Future?> getAssetDuplicates({ Future? abortTrigger, }) async { + final response = await getAssetDuplicatesWithHttpInfo(abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -173,7 +176,7 @@ class DuplicatesApi { /// Parameters: /// /// * [DuplicateResolveDto] duplicateResolveDto (required): - Future resolveDuplicatesWithHttpInfo(DuplicateResolveDto duplicateResolveDto,) async { + Future resolveDuplicatesWithHttpInfo(DuplicateResolveDto duplicateResolveDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/duplicates/resolve'; @@ -195,6 +198,7 @@ class DuplicatesApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -205,8 +209,8 @@ class DuplicatesApi { /// Parameters: /// /// * [DuplicateResolveDto] duplicateResolveDto (required): - Future?> resolveDuplicates(DuplicateResolveDto duplicateResolveDto,) async { - final response = await resolveDuplicatesWithHttpInfo(duplicateResolveDto,); + Future?> resolveDuplicates(DuplicateResolveDto duplicateResolveDto, { Future? abortTrigger, }) async { + final response = await resolveDuplicatesWithHttpInfo(duplicateResolveDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } diff --git a/mobile/openapi/lib/api/faces_api.dart b/mobile/openapi/lib/api/faces_api.dart index 43d63b47b9..2a71bbbaca 100644 --- a/mobile/openapi/lib/api/faces_api.dart +++ b/mobile/openapi/lib/api/faces_api.dart @@ -25,7 +25,7 @@ class FacesApi { /// Parameters: /// /// * [AssetFaceCreateDto] assetFaceCreateDto (required): - Future createFaceWithHttpInfo(AssetFaceCreateDto assetFaceCreateDto,) async { + Future createFaceWithHttpInfo(AssetFaceCreateDto assetFaceCreateDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/faces'; @@ -47,6 +47,7 @@ class FacesApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -57,8 +58,8 @@ class FacesApi { /// Parameters: /// /// * [AssetFaceCreateDto] assetFaceCreateDto (required): - Future createFace(AssetFaceCreateDto assetFaceCreateDto,) async { - final response = await createFaceWithHttpInfo(assetFaceCreateDto,); + Future createFace(AssetFaceCreateDto assetFaceCreateDto, { Future? abortTrigger, }) async { + final response = await createFaceWithHttpInfo(assetFaceCreateDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -75,7 +76,7 @@ class FacesApi { /// * [String] id (required): /// /// * [AssetFaceDeleteDto] assetFaceDeleteDto (required): - Future deleteFaceWithHttpInfo(String id, AssetFaceDeleteDto assetFaceDeleteDto,) async { + Future deleteFaceWithHttpInfo(String id, AssetFaceDeleteDto assetFaceDeleteDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/faces/{id}' .replaceAll('{id}', id); @@ -98,6 +99,7 @@ class FacesApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -110,8 +112,8 @@ class FacesApi { /// * [String] id (required): /// /// * [AssetFaceDeleteDto] assetFaceDeleteDto (required): - Future deleteFace(String id, AssetFaceDeleteDto assetFaceDeleteDto,) async { - final response = await deleteFaceWithHttpInfo(id, assetFaceDeleteDto,); + Future deleteFace(String id, AssetFaceDeleteDto assetFaceDeleteDto, { Future? abortTrigger, }) async { + final response = await deleteFaceWithHttpInfo(id, assetFaceDeleteDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -127,7 +129,7 @@ class FacesApi { /// /// * [String] id (required): /// Face ID - Future getFacesWithHttpInfo(String id,) async { + Future getFacesWithHttpInfo(String id, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/faces'; @@ -151,6 +153,7 @@ class FacesApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -162,8 +165,8 @@ class FacesApi { /// /// * [String] id (required): /// Face ID - Future?> getFaces(String id,) async { - final response = await getFacesWithHttpInfo(id,); + Future?> getFaces(String id, { Future? abortTrigger, }) async { + final response = await getFacesWithHttpInfo(id, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -191,7 +194,7 @@ class FacesApi { /// * [String] id (required): /// /// * [FaceDto] faceDto (required): - Future reassignFacesByIdWithHttpInfo(String id, FaceDto faceDto,) async { + Future reassignFacesByIdWithHttpInfo(String id, FaceDto faceDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/faces/{id}' .replaceAll('{id}', id); @@ -214,6 +217,7 @@ class FacesApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -226,8 +230,8 @@ class FacesApi { /// * [String] id (required): /// /// * [FaceDto] faceDto (required): - Future reassignFacesById(String id, FaceDto faceDto,) async { - final response = await reassignFacesByIdWithHttpInfo(id, faceDto,); + Future reassignFacesById(String id, FaceDto faceDto, { Future? abortTrigger, }) async { + final response = await reassignFacesByIdWithHttpInfo(id, faceDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } diff --git a/mobile/openapi/lib/api/jobs_api.dart b/mobile/openapi/lib/api/jobs_api.dart index 9dda59a883..287432ad9a 100644 --- a/mobile/openapi/lib/api/jobs_api.dart +++ b/mobile/openapi/lib/api/jobs_api.dart @@ -25,7 +25,7 @@ class JobsApi { /// Parameters: /// /// * [JobCreateDto] jobCreateDto (required): - Future createJobWithHttpInfo(JobCreateDto jobCreateDto,) async { + Future createJobWithHttpInfo(JobCreateDto jobCreateDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/jobs'; @@ -47,6 +47,7 @@ class JobsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -57,8 +58,8 @@ class JobsApi { /// Parameters: /// /// * [JobCreateDto] jobCreateDto (required): - Future createJob(JobCreateDto jobCreateDto,) async { - final response = await createJobWithHttpInfo(jobCreateDto,); + Future createJob(JobCreateDto jobCreateDto, { Future? abortTrigger, }) async { + final response = await createJobWithHttpInfo(jobCreateDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -69,7 +70,7 @@ class JobsApi { /// Retrieve the counts of the current queue, as well as the current status. /// /// Note: This method returns the HTTP [Response]. - Future getQueuesLegacyWithHttpInfo() async { + Future getQueuesLegacyWithHttpInfo({ Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/jobs'; @@ -91,14 +92,15 @@ class JobsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } /// Retrieve queue counts and status /// /// Retrieve the counts of the current queue, as well as the current status. - Future getQueuesLegacy() async { - final response = await getQueuesLegacyWithHttpInfo(); + Future getQueuesLegacy({ Future? abortTrigger, }) async { + final response = await getQueuesLegacyWithHttpInfo(abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -123,7 +125,7 @@ class JobsApi { /// * [QueueName] name (required): /// /// * [QueueCommandDto] queueCommandDto (required): - Future runQueueCommandLegacyWithHttpInfo(QueueName name, QueueCommandDto queueCommandDto,) async { + Future runQueueCommandLegacyWithHttpInfo(QueueName name, QueueCommandDto queueCommandDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/jobs/{name}' .replaceAll('{name}', name.toString()); @@ -146,6 +148,7 @@ class JobsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -158,8 +161,8 @@ class JobsApi { /// * [QueueName] name (required): /// /// * [QueueCommandDto] queueCommandDto (required): - Future runQueueCommandLegacy(QueueName name, QueueCommandDto queueCommandDto,) async { - final response = await runQueueCommandLegacyWithHttpInfo(name, queueCommandDto,); + Future runQueueCommandLegacy(QueueName name, QueueCommandDto queueCommandDto, { Future? abortTrigger, }) async { + final response = await runQueueCommandLegacyWithHttpInfo(name, queueCommandDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } diff --git a/mobile/openapi/lib/api/libraries_api.dart b/mobile/openapi/lib/api/libraries_api.dart index ca59f823fe..a3b3086994 100644 --- a/mobile/openapi/lib/api/libraries_api.dart +++ b/mobile/openapi/lib/api/libraries_api.dart @@ -25,7 +25,7 @@ class LibrariesApi { /// Parameters: /// /// * [CreateLibraryDto] createLibraryDto (required): - Future createLibraryWithHttpInfo(CreateLibraryDto createLibraryDto,) async { + Future createLibraryWithHttpInfo(CreateLibraryDto createLibraryDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/libraries'; @@ -47,6 +47,7 @@ class LibrariesApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -57,8 +58,8 @@ class LibrariesApi { /// Parameters: /// /// * [CreateLibraryDto] createLibraryDto (required): - Future createLibrary(CreateLibraryDto createLibraryDto,) async { - final response = await createLibraryWithHttpInfo(createLibraryDto,); + Future createLibrary(CreateLibraryDto createLibraryDto, { Future? abortTrigger, }) async { + final response = await createLibraryWithHttpInfo(createLibraryDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -81,7 +82,7 @@ class LibrariesApi { /// Parameters: /// /// * [String] id (required): - Future deleteLibraryWithHttpInfo(String id,) async { + Future deleteLibraryWithHttpInfo(String id, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/libraries/{id}' .replaceAll('{id}', id); @@ -104,6 +105,7 @@ class LibrariesApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -114,8 +116,8 @@ class LibrariesApi { /// Parameters: /// /// * [String] id (required): - Future deleteLibrary(String id,) async { - final response = await deleteLibraryWithHttpInfo(id,); + Future deleteLibrary(String id, { Future? abortTrigger, }) async { + final response = await deleteLibraryWithHttpInfo(id, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -126,7 +128,7 @@ class LibrariesApi { /// Retrieve a list of external libraries. /// /// Note: This method returns the HTTP [Response]. - Future getAllLibrariesWithHttpInfo() async { + Future getAllLibrariesWithHttpInfo({ Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/libraries'; @@ -148,14 +150,15 @@ class LibrariesApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } /// Retrieve libraries /// /// Retrieve a list of external libraries. - Future?> getAllLibraries() async { - final response = await getAllLibrariesWithHttpInfo(); + Future?> getAllLibraries({ Future? abortTrigger, }) async { + final response = await getAllLibrariesWithHttpInfo(abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -181,7 +184,7 @@ class LibrariesApi { /// Parameters: /// /// * [String] id (required): - Future getLibraryWithHttpInfo(String id,) async { + Future getLibraryWithHttpInfo(String id, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/libraries/{id}' .replaceAll('{id}', id); @@ -204,6 +207,7 @@ class LibrariesApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -214,8 +218,8 @@ class LibrariesApi { /// Parameters: /// /// * [String] id (required): - Future getLibrary(String id,) async { - final response = await getLibraryWithHttpInfo(id,); + Future getLibrary(String id, { Future? abortTrigger, }) async { + final response = await getLibraryWithHttpInfo(id, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -238,7 +242,7 @@ class LibrariesApi { /// Parameters: /// /// * [String] id (required): - Future getLibraryStatisticsWithHttpInfo(String id,) async { + Future getLibraryStatisticsWithHttpInfo(String id, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/libraries/{id}/statistics' .replaceAll('{id}', id); @@ -261,6 +265,7 @@ class LibrariesApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -271,8 +276,8 @@ class LibrariesApi { /// Parameters: /// /// * [String] id (required): - Future getLibraryStatistics(String id,) async { - final response = await getLibraryStatisticsWithHttpInfo(id,); + Future getLibraryStatistics(String id, { Future? abortTrigger, }) async { + final response = await getLibraryStatisticsWithHttpInfo(id, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -295,7 +300,7 @@ class LibrariesApi { /// Parameters: /// /// * [String] id (required): - Future scanLibraryWithHttpInfo(String id,) async { + Future scanLibraryWithHttpInfo(String id, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/libraries/{id}/scan' .replaceAll('{id}', id); @@ -318,6 +323,7 @@ class LibrariesApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -328,8 +334,8 @@ class LibrariesApi { /// Parameters: /// /// * [String] id (required): - Future scanLibrary(String id,) async { - final response = await scanLibraryWithHttpInfo(id,); + Future scanLibrary(String id, { Future? abortTrigger, }) async { + final response = await scanLibraryWithHttpInfo(id, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -346,7 +352,7 @@ class LibrariesApi { /// * [String] id (required): /// /// * [UpdateLibraryDto] updateLibraryDto (required): - Future updateLibraryWithHttpInfo(String id, UpdateLibraryDto updateLibraryDto,) async { + Future updateLibraryWithHttpInfo(String id, UpdateLibraryDto updateLibraryDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/libraries/{id}' .replaceAll('{id}', id); @@ -369,6 +375,7 @@ class LibrariesApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -381,8 +388,8 @@ class LibrariesApi { /// * [String] id (required): /// /// * [UpdateLibraryDto] updateLibraryDto (required): - Future updateLibrary(String id, UpdateLibraryDto updateLibraryDto,) async { - final response = await updateLibraryWithHttpInfo(id, updateLibraryDto,); + Future updateLibrary(String id, UpdateLibraryDto updateLibraryDto, { Future? abortTrigger, }) async { + final response = await updateLibraryWithHttpInfo(id, updateLibraryDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -407,7 +414,7 @@ class LibrariesApi { /// * [String] id (required): /// /// * [ValidateLibraryDto] validateLibraryDto (required): - Future validateWithHttpInfo(String id, ValidateLibraryDto validateLibraryDto,) async { + Future validateWithHttpInfo(String id, ValidateLibraryDto validateLibraryDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/libraries/{id}/validate' .replaceAll('{id}', id); @@ -430,6 +437,7 @@ class LibrariesApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -442,8 +450,8 @@ class LibrariesApi { /// * [String] id (required): /// /// * [ValidateLibraryDto] validateLibraryDto (required): - Future validate(String id, ValidateLibraryDto validateLibraryDto,) async { - final response = await validateWithHttpInfo(id, validateLibraryDto,); + Future validate(String id, ValidateLibraryDto validateLibraryDto, { Future? abortTrigger, }) async { + final response = await validateWithHttpInfo(id, validateLibraryDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } diff --git a/mobile/openapi/lib/api/maintenance_admin_api.dart b/mobile/openapi/lib/api/maintenance_admin_api.dart index 0f953f1634..8bab193ddf 100644 --- a/mobile/openapi/lib/api/maintenance_admin_api.dart +++ b/mobile/openapi/lib/api/maintenance_admin_api.dart @@ -21,7 +21,7 @@ class MaintenanceAdminApi { /// Collect integrity checks and other heuristics about local data. /// /// Note: This method returns the HTTP [Response]. - Future detectPriorInstallWithHttpInfo() async { + Future detectPriorInstallWithHttpInfo({ Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/admin/maintenance/detect-install'; @@ -43,14 +43,15 @@ class MaintenanceAdminApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } /// Detect existing install /// /// Collect integrity checks and other heuristics about local data. - Future detectPriorInstall() async { - final response = await detectPriorInstallWithHttpInfo(); + Future detectPriorInstall({ Future? abortTrigger, }) async { + final response = await detectPriorInstallWithHttpInfo(abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -69,7 +70,7 @@ class MaintenanceAdminApi { /// Fetch information about the currently running maintenance action. /// /// Note: This method returns the HTTP [Response]. - Future getMaintenanceStatusWithHttpInfo() async { + Future getMaintenanceStatusWithHttpInfo({ Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/admin/maintenance/status'; @@ -91,14 +92,15 @@ class MaintenanceAdminApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } /// Get maintenance mode status /// /// Fetch information about the currently running maintenance action. - Future getMaintenanceStatus() async { - final response = await getMaintenanceStatusWithHttpInfo(); + Future getMaintenanceStatus({ Future? abortTrigger, }) async { + final response = await getMaintenanceStatusWithHttpInfo(abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -121,7 +123,7 @@ class MaintenanceAdminApi { /// Parameters: /// /// * [MaintenanceLoginDto] maintenanceLoginDto (required): - Future maintenanceLoginWithHttpInfo(MaintenanceLoginDto maintenanceLoginDto,) async { + Future maintenanceLoginWithHttpInfo(MaintenanceLoginDto maintenanceLoginDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/admin/maintenance/login'; @@ -143,6 +145,7 @@ class MaintenanceAdminApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -153,8 +156,8 @@ class MaintenanceAdminApi { /// Parameters: /// /// * [MaintenanceLoginDto] maintenanceLoginDto (required): - Future maintenanceLogin(MaintenanceLoginDto maintenanceLoginDto,) async { - final response = await maintenanceLoginWithHttpInfo(maintenanceLoginDto,); + Future maintenanceLogin(MaintenanceLoginDto maintenanceLoginDto, { Future? abortTrigger, }) async { + final response = await maintenanceLoginWithHttpInfo(maintenanceLoginDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -177,7 +180,7 @@ class MaintenanceAdminApi { /// Parameters: /// /// * [SetMaintenanceModeDto] setMaintenanceModeDto (required): - Future setMaintenanceModeWithHttpInfo(SetMaintenanceModeDto setMaintenanceModeDto,) async { + Future setMaintenanceModeWithHttpInfo(SetMaintenanceModeDto setMaintenanceModeDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/admin/maintenance'; @@ -199,6 +202,7 @@ class MaintenanceAdminApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -209,8 +213,8 @@ class MaintenanceAdminApi { /// Parameters: /// /// * [SetMaintenanceModeDto] setMaintenanceModeDto (required): - Future setMaintenanceMode(SetMaintenanceModeDto setMaintenanceModeDto,) async { - final response = await setMaintenanceModeWithHttpInfo(setMaintenanceModeDto,); + Future setMaintenanceMode(SetMaintenanceModeDto setMaintenanceModeDto, { Future? abortTrigger, }) async { + final response = await setMaintenanceModeWithHttpInfo(setMaintenanceModeDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } diff --git a/mobile/openapi/lib/api/map_api.dart b/mobile/openapi/lib/api/map_api.dart index 4ce62bd96c..7e1618a875 100644 --- a/mobile/openapi/lib/api/map_api.dart +++ b/mobile/openapi/lib/api/map_api.dart @@ -41,7 +41,7 @@ class MapApi { /// /// * [bool] withSharedAlbums: /// Include shared album assets - Future getMapMarkersWithHttpInfo({ DateTime? fileCreatedAfter, DateTime? fileCreatedBefore, bool? isArchived, bool? isFavorite, bool? withPartners, bool? withSharedAlbums, }) async { + Future getMapMarkersWithHttpInfo({ DateTime? fileCreatedAfter, DateTime? fileCreatedBefore, bool? isArchived, bool? isFavorite, bool? withPartners, bool? withSharedAlbums, Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/map/markers'; @@ -82,6 +82,7 @@ class MapApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -108,8 +109,8 @@ class MapApi { /// /// * [bool] withSharedAlbums: /// Include shared album assets - Future?> getMapMarkers({ DateTime? fileCreatedAfter, DateTime? fileCreatedBefore, bool? isArchived, bool? isFavorite, bool? withPartners, bool? withSharedAlbums, }) async { - final response = await getMapMarkersWithHttpInfo( fileCreatedAfter: fileCreatedAfter, fileCreatedBefore: fileCreatedBefore, isArchived: isArchived, isFavorite: isFavorite, withPartners: withPartners, withSharedAlbums: withSharedAlbums, ); + Future?> getMapMarkers({ DateTime? fileCreatedAfter, DateTime? fileCreatedBefore, bool? isArchived, bool? isFavorite, bool? withPartners, bool? withSharedAlbums, Future? abortTrigger, }) async { + final response = await getMapMarkersWithHttpInfo(fileCreatedAfter: fileCreatedAfter, fileCreatedBefore: fileCreatedBefore, isArchived: isArchived, isFavorite: isFavorite, withPartners: withPartners, withSharedAlbums: withSharedAlbums, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -139,7 +140,7 @@ class MapApi { /// /// * [double] lon (required): /// Longitude (-180 to 180) - Future reverseGeocodeWithHttpInfo(double lat, double lon,) async { + Future reverseGeocodeWithHttpInfo(double lat, double lon, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/map/reverse-geocode'; @@ -164,6 +165,7 @@ class MapApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -178,8 +180,8 @@ class MapApi { /// /// * [double] lon (required): /// Longitude (-180 to 180) - Future?> reverseGeocode(double lat, double lon,) async { - final response = await reverseGeocodeWithHttpInfo(lat, lon,); + Future?> reverseGeocode(double lat, double lon, { Future? abortTrigger, }) async { + final response = await reverseGeocodeWithHttpInfo(lat, lon, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } diff --git a/mobile/openapi/lib/api/memories_api.dart b/mobile/openapi/lib/api/memories_api.dart index 0cd96ac442..f5c653765a 100644 --- a/mobile/openapi/lib/api/memories_api.dart +++ b/mobile/openapi/lib/api/memories_api.dart @@ -27,7 +27,7 @@ class MemoriesApi { /// * [String] id (required): /// /// * [BulkIdsDto] bulkIdsDto (required): - Future addMemoryAssetsWithHttpInfo(String id, BulkIdsDto bulkIdsDto,) async { + Future addMemoryAssetsWithHttpInfo(String id, BulkIdsDto bulkIdsDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/memories/{id}/assets' .replaceAll('{id}', id); @@ -50,6 +50,7 @@ class MemoriesApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -62,8 +63,8 @@ class MemoriesApi { /// * [String] id (required): /// /// * [BulkIdsDto] bulkIdsDto (required): - Future?> addMemoryAssets(String id, BulkIdsDto bulkIdsDto,) async { - final response = await addMemoryAssetsWithHttpInfo(id, bulkIdsDto,); + Future?> addMemoryAssets(String id, BulkIdsDto bulkIdsDto, { Future? abortTrigger, }) async { + final response = await addMemoryAssetsWithHttpInfo(id, bulkIdsDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -89,7 +90,7 @@ class MemoriesApi { /// Parameters: /// /// * [MemoryCreateDto] memoryCreateDto (required): - Future createMemoryWithHttpInfo(MemoryCreateDto memoryCreateDto,) async { + Future createMemoryWithHttpInfo(MemoryCreateDto memoryCreateDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/memories'; @@ -111,6 +112,7 @@ class MemoriesApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -121,8 +123,8 @@ class MemoriesApi { /// Parameters: /// /// * [MemoryCreateDto] memoryCreateDto (required): - Future createMemory(MemoryCreateDto memoryCreateDto,) async { - final response = await createMemoryWithHttpInfo(memoryCreateDto,); + Future createMemory(MemoryCreateDto memoryCreateDto, { Future? abortTrigger, }) async { + final response = await createMemoryWithHttpInfo(memoryCreateDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -145,7 +147,7 @@ class MemoriesApi { /// Parameters: /// /// * [String] id (required): - Future deleteMemoryWithHttpInfo(String id,) async { + Future deleteMemoryWithHttpInfo(String id, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/memories/{id}' .replaceAll('{id}', id); @@ -168,6 +170,7 @@ class MemoriesApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -178,8 +181,8 @@ class MemoriesApi { /// Parameters: /// /// * [String] id (required): - Future deleteMemory(String id,) async { - final response = await deleteMemoryWithHttpInfo(id,); + Future deleteMemory(String id, { Future? abortTrigger, }) async { + final response = await deleteMemoryWithHttpInfo(id, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -194,7 +197,7 @@ class MemoriesApi { /// Parameters: /// /// * [String] id (required): - Future getMemoryWithHttpInfo(String id,) async { + Future getMemoryWithHttpInfo(String id, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/memories/{id}' .replaceAll('{id}', id); @@ -217,6 +220,7 @@ class MemoriesApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -227,8 +231,8 @@ class MemoriesApi { /// Parameters: /// /// * [String] id (required): - Future getMemory(String id,) async { - final response = await getMemoryWithHttpInfo(id,); + Future getMemory(String id, { Future? abortTrigger, }) async { + final response = await getMemoryWithHttpInfo(id, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -265,7 +269,7 @@ class MemoriesApi { /// Number of memories to return /// /// * [MemoryType] type: - Future memoriesStatisticsWithHttpInfo({ DateTime? for_, bool? isSaved, bool? isTrashed, MemorySearchOrder? order, int? size, MemoryType? type, }) async { + Future memoriesStatisticsWithHttpInfo({ DateTime? for_, bool? isSaved, bool? isTrashed, MemorySearchOrder? order, int? size, MemoryType? type, Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/memories/statistics'; @@ -306,6 +310,7 @@ class MemoriesApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -330,8 +335,8 @@ class MemoriesApi { /// Number of memories to return /// /// * [MemoryType] type: - Future memoriesStatistics({ DateTime? for_, bool? isSaved, bool? isTrashed, MemorySearchOrder? order, int? size, MemoryType? type, }) async { - final response = await memoriesStatisticsWithHttpInfo( for_: for_, isSaved: isSaved, isTrashed: isTrashed, order: order, size: size, type: type, ); + Future memoriesStatistics({ DateTime? for_, bool? isSaved, bool? isTrashed, MemorySearchOrder? order, int? size, MemoryType? type, Future? abortTrigger, }) async { + final response = await memoriesStatisticsWithHttpInfo(for_: for_, isSaved: isSaved, isTrashed: isTrashed, order: order, size: size, type: type, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -356,7 +361,7 @@ class MemoriesApi { /// * [String] id (required): /// /// * [BulkIdsDto] bulkIdsDto (required): - Future removeMemoryAssetsWithHttpInfo(String id, BulkIdsDto bulkIdsDto,) async { + Future removeMemoryAssetsWithHttpInfo(String id, BulkIdsDto bulkIdsDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/memories/{id}/assets' .replaceAll('{id}', id); @@ -379,6 +384,7 @@ class MemoriesApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -391,8 +397,8 @@ class MemoriesApi { /// * [String] id (required): /// /// * [BulkIdsDto] bulkIdsDto (required): - Future?> removeMemoryAssets(String id, BulkIdsDto bulkIdsDto,) async { - final response = await removeMemoryAssetsWithHttpInfo(id, bulkIdsDto,); + Future?> removeMemoryAssets(String id, BulkIdsDto bulkIdsDto, { Future? abortTrigger, }) async { + final response = await removeMemoryAssetsWithHttpInfo(id, bulkIdsDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -432,7 +438,7 @@ class MemoriesApi { /// Number of memories to return /// /// * [MemoryType] type: - Future searchMemoriesWithHttpInfo({ DateTime? for_, bool? isSaved, bool? isTrashed, MemorySearchOrder? order, int? size, MemoryType? type, }) async { + Future searchMemoriesWithHttpInfo({ DateTime? for_, bool? isSaved, bool? isTrashed, MemorySearchOrder? order, int? size, MemoryType? type, Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/memories'; @@ -473,6 +479,7 @@ class MemoriesApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -497,8 +504,8 @@ class MemoriesApi { /// Number of memories to return /// /// * [MemoryType] type: - Future?> searchMemories({ DateTime? for_, bool? isSaved, bool? isTrashed, MemorySearchOrder? order, int? size, MemoryType? type, }) async { - final response = await searchMemoriesWithHttpInfo( for_: for_, isSaved: isSaved, isTrashed: isTrashed, order: order, size: size, type: type, ); + Future?> searchMemories({ DateTime? for_, bool? isSaved, bool? isTrashed, MemorySearchOrder? order, int? size, MemoryType? type, Future? abortTrigger, }) async { + final response = await searchMemoriesWithHttpInfo(for_: for_, isSaved: isSaved, isTrashed: isTrashed, order: order, size: size, type: type, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -526,7 +533,7 @@ class MemoriesApi { /// * [String] id (required): /// /// * [MemoryUpdateDto] memoryUpdateDto (required): - Future updateMemoryWithHttpInfo(String id, MemoryUpdateDto memoryUpdateDto,) async { + Future updateMemoryWithHttpInfo(String id, MemoryUpdateDto memoryUpdateDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/memories/{id}' .replaceAll('{id}', id); @@ -549,6 +556,7 @@ class MemoriesApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -561,8 +569,8 @@ class MemoriesApi { /// * [String] id (required): /// /// * [MemoryUpdateDto] memoryUpdateDto (required): - Future updateMemory(String id, MemoryUpdateDto memoryUpdateDto,) async { - final response = await updateMemoryWithHttpInfo(id, memoryUpdateDto,); + Future updateMemory(String id, MemoryUpdateDto memoryUpdateDto, { Future? abortTrigger, }) async { + final response = await updateMemoryWithHttpInfo(id, memoryUpdateDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } diff --git a/mobile/openapi/lib/api/notifications_admin_api.dart b/mobile/openapi/lib/api/notifications_admin_api.dart index 7821553d30..e9e18e791e 100644 --- a/mobile/openapi/lib/api/notifications_admin_api.dart +++ b/mobile/openapi/lib/api/notifications_admin_api.dart @@ -25,7 +25,7 @@ class NotificationsAdminApi { /// Parameters: /// /// * [NotificationCreateDto] notificationCreateDto (required): - Future createNotificationWithHttpInfo(NotificationCreateDto notificationCreateDto,) async { + Future createNotificationWithHttpInfo(NotificationCreateDto notificationCreateDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/admin/notifications'; @@ -47,6 +47,7 @@ class NotificationsAdminApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -57,8 +58,8 @@ class NotificationsAdminApi { /// Parameters: /// /// * [NotificationCreateDto] notificationCreateDto (required): - Future createNotification(NotificationCreateDto notificationCreateDto,) async { - final response = await createNotificationWithHttpInfo(notificationCreateDto,); + Future createNotification(NotificationCreateDto notificationCreateDto, { Future? abortTrigger, }) async { + final response = await createNotificationWithHttpInfo(notificationCreateDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -83,7 +84,7 @@ class NotificationsAdminApi { /// * [String] name (required): /// /// * [TemplateDto] templateDto (required): - Future getNotificationTemplateAdminWithHttpInfo(String name, TemplateDto templateDto,) async { + Future getNotificationTemplateAdminWithHttpInfo(String name, TemplateDto templateDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/admin/notifications/templates/{name}' .replaceAll('{name}', name); @@ -106,6 +107,7 @@ class NotificationsAdminApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -118,8 +120,8 @@ class NotificationsAdminApi { /// * [String] name (required): /// /// * [TemplateDto] templateDto (required): - Future getNotificationTemplateAdmin(String name, TemplateDto templateDto,) async { - final response = await getNotificationTemplateAdminWithHttpInfo(name, templateDto,); + Future getNotificationTemplateAdmin(String name, TemplateDto templateDto, { Future? abortTrigger, }) async { + final response = await getNotificationTemplateAdminWithHttpInfo(name, templateDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -142,7 +144,7 @@ class NotificationsAdminApi { /// Parameters: /// /// * [SystemConfigSmtpDto] systemConfigSmtpDto (required): - Future sendTestEmailAdminWithHttpInfo(SystemConfigSmtpDto systemConfigSmtpDto,) async { + Future sendTestEmailAdminWithHttpInfo(SystemConfigSmtpDto systemConfigSmtpDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/admin/notifications/test-email'; @@ -164,6 +166,7 @@ class NotificationsAdminApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -174,8 +177,8 @@ class NotificationsAdminApi { /// Parameters: /// /// * [SystemConfigSmtpDto] systemConfigSmtpDto (required): - Future sendTestEmailAdmin(SystemConfigSmtpDto systemConfigSmtpDto,) async { - final response = await sendTestEmailAdminWithHttpInfo(systemConfigSmtpDto,); + Future sendTestEmailAdmin(SystemConfigSmtpDto systemConfigSmtpDto, { Future? abortTrigger, }) async { + final response = await sendTestEmailAdminWithHttpInfo(systemConfigSmtpDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } diff --git a/mobile/openapi/lib/api/notifications_api.dart b/mobile/openapi/lib/api/notifications_api.dart index ab0be3e8f3..6b4f213bcd 100644 --- a/mobile/openapi/lib/api/notifications_api.dart +++ b/mobile/openapi/lib/api/notifications_api.dart @@ -25,7 +25,7 @@ class NotificationsApi { /// Parameters: /// /// * [String] id (required): - Future deleteNotificationWithHttpInfo(String id,) async { + Future deleteNotificationWithHttpInfo(String id, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/notifications/{id}' .replaceAll('{id}', id); @@ -48,6 +48,7 @@ class NotificationsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -58,8 +59,8 @@ class NotificationsApi { /// Parameters: /// /// * [String] id (required): - Future deleteNotification(String id,) async { - final response = await deleteNotificationWithHttpInfo(id,); + Future deleteNotification(String id, { Future? abortTrigger, }) async { + final response = await deleteNotificationWithHttpInfo(id, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -74,7 +75,7 @@ class NotificationsApi { /// Parameters: /// /// * [NotificationDeleteAllDto] notificationDeleteAllDto (required): - Future deleteNotificationsWithHttpInfo(NotificationDeleteAllDto notificationDeleteAllDto,) async { + Future deleteNotificationsWithHttpInfo(NotificationDeleteAllDto notificationDeleteAllDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/notifications'; @@ -96,6 +97,7 @@ class NotificationsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -106,8 +108,8 @@ class NotificationsApi { /// Parameters: /// /// * [NotificationDeleteAllDto] notificationDeleteAllDto (required): - Future deleteNotifications(NotificationDeleteAllDto notificationDeleteAllDto,) async { - final response = await deleteNotificationsWithHttpInfo(notificationDeleteAllDto,); + Future deleteNotifications(NotificationDeleteAllDto notificationDeleteAllDto, { Future? abortTrigger, }) async { + final response = await deleteNotificationsWithHttpInfo(notificationDeleteAllDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -122,7 +124,7 @@ class NotificationsApi { /// Parameters: /// /// * [String] id (required): - Future getNotificationWithHttpInfo(String id,) async { + Future getNotificationWithHttpInfo(String id, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/notifications/{id}' .replaceAll('{id}', id); @@ -145,6 +147,7 @@ class NotificationsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -155,8 +158,8 @@ class NotificationsApi { /// Parameters: /// /// * [String] id (required): - Future getNotification(String id,) async { - final response = await getNotificationWithHttpInfo(id,); + Future getNotification(String id, { Future? abortTrigger, }) async { + final response = await getNotificationWithHttpInfo(id, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -187,7 +190,7 @@ class NotificationsApi { /// /// * [bool] unread: /// Filter by unread status - Future getNotificationsWithHttpInfo({ String? id, NotificationLevel? level, NotificationType? type, bool? unread, }) async { + Future getNotificationsWithHttpInfo({ String? id, NotificationLevel? level, NotificationType? type, bool? unread, Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/notifications'; @@ -222,6 +225,7 @@ class NotificationsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -240,8 +244,8 @@ class NotificationsApi { /// /// * [bool] unread: /// Filter by unread status - Future?> getNotifications({ String? id, NotificationLevel? level, NotificationType? type, bool? unread, }) async { - final response = await getNotificationsWithHttpInfo( id: id, level: level, type: type, unread: unread, ); + Future?> getNotifications({ String? id, NotificationLevel? level, NotificationType? type, bool? unread, Future? abortTrigger, }) async { + final response = await getNotificationsWithHttpInfo(id: id, level: level, type: type, unread: unread, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -269,7 +273,7 @@ class NotificationsApi { /// * [String] id (required): /// /// * [NotificationUpdateDto] notificationUpdateDto (required): - Future updateNotificationWithHttpInfo(String id, NotificationUpdateDto notificationUpdateDto,) async { + Future updateNotificationWithHttpInfo(String id, NotificationUpdateDto notificationUpdateDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/notifications/{id}' .replaceAll('{id}', id); @@ -292,6 +296,7 @@ class NotificationsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -304,8 +309,8 @@ class NotificationsApi { /// * [String] id (required): /// /// * [NotificationUpdateDto] notificationUpdateDto (required): - Future updateNotification(String id, NotificationUpdateDto notificationUpdateDto,) async { - final response = await updateNotificationWithHttpInfo(id, notificationUpdateDto,); + Future updateNotification(String id, NotificationUpdateDto notificationUpdateDto, { Future? abortTrigger, }) async { + final response = await updateNotificationWithHttpInfo(id, notificationUpdateDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -328,7 +333,7 @@ class NotificationsApi { /// Parameters: /// /// * [NotificationUpdateAllDto] notificationUpdateAllDto (required): - Future updateNotificationsWithHttpInfo(NotificationUpdateAllDto notificationUpdateAllDto,) async { + Future updateNotificationsWithHttpInfo(NotificationUpdateAllDto notificationUpdateAllDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/notifications'; @@ -350,6 +355,7 @@ class NotificationsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -360,8 +366,8 @@ class NotificationsApi { /// Parameters: /// /// * [NotificationUpdateAllDto] notificationUpdateAllDto (required): - Future updateNotifications(NotificationUpdateAllDto notificationUpdateAllDto,) async { - final response = await updateNotificationsWithHttpInfo(notificationUpdateAllDto,); + Future updateNotifications(NotificationUpdateAllDto notificationUpdateAllDto, { Future? abortTrigger, }) async { + final response = await updateNotificationsWithHttpInfo(notificationUpdateAllDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } diff --git a/mobile/openapi/lib/api/partners_api.dart b/mobile/openapi/lib/api/partners_api.dart index 7d18f6d867..45bcdcd085 100644 --- a/mobile/openapi/lib/api/partners_api.dart +++ b/mobile/openapi/lib/api/partners_api.dart @@ -25,7 +25,7 @@ class PartnersApi { /// Parameters: /// /// * [PartnerCreateDto] partnerCreateDto (required): - Future createPartnerWithHttpInfo(PartnerCreateDto partnerCreateDto,) async { + Future createPartnerWithHttpInfo(PartnerCreateDto partnerCreateDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/partners'; @@ -47,6 +47,7 @@ class PartnersApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -57,8 +58,8 @@ class PartnersApi { /// Parameters: /// /// * [PartnerCreateDto] partnerCreateDto (required): - Future createPartner(PartnerCreateDto partnerCreateDto,) async { - final response = await createPartnerWithHttpInfo(partnerCreateDto,); + Future createPartner(PartnerCreateDto partnerCreateDto, { Future? abortTrigger, }) async { + final response = await createPartnerWithHttpInfo(partnerCreateDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -81,7 +82,7 @@ class PartnersApi { /// Parameters: /// /// * [String] id (required): - Future createPartnerDeprecatedWithHttpInfo(String id,) async { + Future createPartnerDeprecatedWithHttpInfo(String id, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/partners/{id}' .replaceAll('{id}', id); @@ -104,6 +105,7 @@ class PartnersApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -114,8 +116,8 @@ class PartnersApi { /// Parameters: /// /// * [String] id (required): - Future createPartnerDeprecated(String id,) async { - final response = await createPartnerDeprecatedWithHttpInfo(id,); + Future createPartnerDeprecated(String id, { Future? abortTrigger, }) async { + final response = await createPartnerDeprecatedWithHttpInfo(id, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -138,7 +140,7 @@ class PartnersApi { /// Parameters: /// /// * [PartnerDirection] direction (required): - Future getPartnersWithHttpInfo(PartnerDirection direction,) async { + Future getPartnersWithHttpInfo(PartnerDirection direction, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/partners'; @@ -162,6 +164,7 @@ class PartnersApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -172,8 +175,8 @@ class PartnersApi { /// Parameters: /// /// * [PartnerDirection] direction (required): - Future?> getPartners(PartnerDirection direction,) async { - final response = await getPartnersWithHttpInfo(direction,); + Future?> getPartners(PartnerDirection direction, { Future? abortTrigger, }) async { + final response = await getPartnersWithHttpInfo(direction, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -199,7 +202,7 @@ class PartnersApi { /// Parameters: /// /// * [String] id (required): - Future removePartnerWithHttpInfo(String id,) async { + Future removePartnerWithHttpInfo(String id, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/partners/{id}' .replaceAll('{id}', id); @@ -222,6 +225,7 @@ class PartnersApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -232,8 +236,8 @@ class PartnersApi { /// Parameters: /// /// * [String] id (required): - Future removePartner(String id,) async { - final response = await removePartnerWithHttpInfo(id,); + Future removePartner(String id, { Future? abortTrigger, }) async { + final response = await removePartnerWithHttpInfo(id, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -250,7 +254,7 @@ class PartnersApi { /// * [String] id (required): /// /// * [PartnerUpdateDto] partnerUpdateDto (required): - Future updatePartnerWithHttpInfo(String id, PartnerUpdateDto partnerUpdateDto,) async { + Future updatePartnerWithHttpInfo(String id, PartnerUpdateDto partnerUpdateDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/partners/{id}' .replaceAll('{id}', id); @@ -273,6 +277,7 @@ class PartnersApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -285,8 +290,8 @@ class PartnersApi { /// * [String] id (required): /// /// * [PartnerUpdateDto] partnerUpdateDto (required): - Future updatePartner(String id, PartnerUpdateDto partnerUpdateDto,) async { - final response = await updatePartnerWithHttpInfo(id, partnerUpdateDto,); + Future updatePartner(String id, PartnerUpdateDto partnerUpdateDto, { Future? abortTrigger, }) async { + final response = await updatePartnerWithHttpInfo(id, partnerUpdateDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } diff --git a/mobile/openapi/lib/api/people_api.dart b/mobile/openapi/lib/api/people_api.dart index 99821f31aa..c35491e110 100644 --- a/mobile/openapi/lib/api/people_api.dart +++ b/mobile/openapi/lib/api/people_api.dart @@ -25,7 +25,7 @@ class PeopleApi { /// Parameters: /// /// * [PersonCreateDto] personCreateDto (required): - Future createPersonWithHttpInfo(PersonCreateDto personCreateDto,) async { + Future createPersonWithHttpInfo(PersonCreateDto personCreateDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/people'; @@ -47,6 +47,7 @@ class PeopleApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -57,8 +58,8 @@ class PeopleApi { /// Parameters: /// /// * [PersonCreateDto] personCreateDto (required): - Future createPerson(PersonCreateDto personCreateDto,) async { - final response = await createPersonWithHttpInfo(personCreateDto,); + Future createPerson(PersonCreateDto personCreateDto, { Future? abortTrigger, }) async { + final response = await createPersonWithHttpInfo(personCreateDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -81,7 +82,7 @@ class PeopleApi { /// Parameters: /// /// * [BulkIdsDto] bulkIdsDto (required): - Future deletePeopleWithHttpInfo(BulkIdsDto bulkIdsDto,) async { + Future deletePeopleWithHttpInfo(BulkIdsDto bulkIdsDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/people'; @@ -103,6 +104,7 @@ class PeopleApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -113,8 +115,8 @@ class PeopleApi { /// Parameters: /// /// * [BulkIdsDto] bulkIdsDto (required): - Future deletePeople(BulkIdsDto bulkIdsDto,) async { - final response = await deletePeopleWithHttpInfo(bulkIdsDto,); + Future deletePeople(BulkIdsDto bulkIdsDto, { Future? abortTrigger, }) async { + final response = await deletePeopleWithHttpInfo(bulkIdsDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -129,7 +131,7 @@ class PeopleApi { /// Parameters: /// /// * [String] id (required): - Future deletePersonWithHttpInfo(String id,) async { + Future deletePersonWithHttpInfo(String id, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/people/{id}' .replaceAll('{id}', id); @@ -152,6 +154,7 @@ class PeopleApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -162,8 +165,8 @@ class PeopleApi { /// Parameters: /// /// * [String] id (required): - Future deletePerson(String id,) async { - final response = await deletePersonWithHttpInfo(id,); + Future deletePerson(String id, { Future? abortTrigger, }) async { + final response = await deletePersonWithHttpInfo(id, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -191,7 +194,7 @@ class PeopleApi { /// /// * [bool] withHidden: /// Include hidden people - Future getAllPeopleWithHttpInfo({ String? closestAssetId, String? closestPersonId, int? page, int? size, bool? withHidden, }) async { + Future getAllPeopleWithHttpInfo({ String? closestAssetId, String? closestPersonId, int? page, int? size, bool? withHidden, Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/people'; @@ -229,6 +232,7 @@ class PeopleApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -252,8 +256,8 @@ class PeopleApi { /// /// * [bool] withHidden: /// Include hidden people - Future getAllPeople({ String? closestAssetId, String? closestPersonId, int? page, int? size, bool? withHidden, }) async { - final response = await getAllPeopleWithHttpInfo( closestAssetId: closestAssetId, closestPersonId: closestPersonId, page: page, size: size, withHidden: withHidden, ); + Future getAllPeople({ String? closestAssetId, String? closestPersonId, int? page, int? size, bool? withHidden, Future? abortTrigger, }) async { + final response = await getAllPeopleWithHttpInfo(closestAssetId: closestAssetId, closestPersonId: closestPersonId, page: page, size: size, withHidden: withHidden, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -276,7 +280,7 @@ class PeopleApi { /// Parameters: /// /// * [String] id (required): - Future getPersonWithHttpInfo(String id,) async { + Future getPersonWithHttpInfo(String id, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/people/{id}' .replaceAll('{id}', id); @@ -299,6 +303,7 @@ class PeopleApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -309,8 +314,8 @@ class PeopleApi { /// Parameters: /// /// * [String] id (required): - Future getPerson(String id,) async { - final response = await getPersonWithHttpInfo(id,); + Future getPerson(String id, { Future? abortTrigger, }) async { + final response = await getPersonWithHttpInfo(id, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -333,7 +338,7 @@ class PeopleApi { /// Parameters: /// /// * [String] id (required): - Future getPersonStatisticsWithHttpInfo(String id,) async { + Future getPersonStatisticsWithHttpInfo(String id, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/people/{id}/statistics' .replaceAll('{id}', id); @@ -356,6 +361,7 @@ class PeopleApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -366,8 +372,8 @@ class PeopleApi { /// Parameters: /// /// * [String] id (required): - Future getPersonStatistics(String id,) async { - final response = await getPersonStatisticsWithHttpInfo(id,); + Future getPersonStatistics(String id, { Future? abortTrigger, }) async { + final response = await getPersonStatisticsWithHttpInfo(id, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -390,7 +396,7 @@ class PeopleApi { /// Parameters: /// /// * [String] id (required): - Future getPersonThumbnailWithHttpInfo(String id,) async { + Future getPersonThumbnailWithHttpInfo(String id, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/people/{id}/thumbnail' .replaceAll('{id}', id); @@ -413,6 +419,7 @@ class PeopleApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -423,8 +430,8 @@ class PeopleApi { /// Parameters: /// /// * [String] id (required): - Future getPersonThumbnail(String id,) async { - final response = await getPersonThumbnailWithHttpInfo(id,); + Future getPersonThumbnail(String id, { Future? abortTrigger, }) async { + final response = await getPersonThumbnailWithHttpInfo(id, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -449,7 +456,7 @@ class PeopleApi { /// * [String] id (required): /// /// * [MergePersonDto] mergePersonDto (required): - Future mergePersonWithHttpInfo(String id, MergePersonDto mergePersonDto,) async { + Future mergePersonWithHttpInfo(String id, MergePersonDto mergePersonDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/people/{id}/merge' .replaceAll('{id}', id); @@ -472,6 +479,7 @@ class PeopleApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -484,8 +492,8 @@ class PeopleApi { /// * [String] id (required): /// /// * [MergePersonDto] mergePersonDto (required): - Future?> mergePerson(String id, MergePersonDto mergePersonDto,) async { - final response = await mergePersonWithHttpInfo(id, mergePersonDto,); + Future?> mergePerson(String id, MergePersonDto mergePersonDto, { Future? abortTrigger, }) async { + final response = await mergePersonWithHttpInfo(id, mergePersonDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -513,7 +521,7 @@ class PeopleApi { /// * [String] id (required): /// /// * [AssetFaceUpdateDto] assetFaceUpdateDto (required): - Future reassignFacesWithHttpInfo(String id, AssetFaceUpdateDto assetFaceUpdateDto,) async { + Future reassignFacesWithHttpInfo(String id, AssetFaceUpdateDto assetFaceUpdateDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/people/{id}/reassign' .replaceAll('{id}', id); @@ -536,6 +544,7 @@ class PeopleApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -548,8 +557,8 @@ class PeopleApi { /// * [String] id (required): /// /// * [AssetFaceUpdateDto] assetFaceUpdateDto (required): - Future?> reassignFaces(String id, AssetFaceUpdateDto assetFaceUpdateDto,) async { - final response = await reassignFacesWithHttpInfo(id, assetFaceUpdateDto,); + Future?> reassignFaces(String id, AssetFaceUpdateDto assetFaceUpdateDto, { Future? abortTrigger, }) async { + final response = await reassignFacesWithHttpInfo(id, assetFaceUpdateDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -575,7 +584,7 @@ class PeopleApi { /// Parameters: /// /// * [PeopleUpdateDto] peopleUpdateDto (required): - Future updatePeopleWithHttpInfo(PeopleUpdateDto peopleUpdateDto,) async { + Future updatePeopleWithHttpInfo(PeopleUpdateDto peopleUpdateDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/people'; @@ -597,6 +606,7 @@ class PeopleApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -607,8 +617,8 @@ class PeopleApi { /// Parameters: /// /// * [PeopleUpdateDto] peopleUpdateDto (required): - Future?> updatePeople(PeopleUpdateDto peopleUpdateDto,) async { - final response = await updatePeopleWithHttpInfo(peopleUpdateDto,); + Future?> updatePeople(PeopleUpdateDto peopleUpdateDto, { Future? abortTrigger, }) async { + final response = await updatePeopleWithHttpInfo(peopleUpdateDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -636,7 +646,7 @@ class PeopleApi { /// * [String] id (required): /// /// * [PersonUpdateDto] personUpdateDto (required): - Future updatePersonWithHttpInfo(String id, PersonUpdateDto personUpdateDto,) async { + Future updatePersonWithHttpInfo(String id, PersonUpdateDto personUpdateDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/people/{id}' .replaceAll('{id}', id); @@ -659,6 +669,7 @@ class PeopleApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -671,8 +682,8 @@ class PeopleApi { /// * [String] id (required): /// /// * [PersonUpdateDto] personUpdateDto (required): - Future updatePerson(String id, PersonUpdateDto personUpdateDto,) async { - final response = await updatePersonWithHttpInfo(id, personUpdateDto,); + Future updatePerson(String id, PersonUpdateDto personUpdateDto, { Future? abortTrigger, }) async { + final response = await updatePersonWithHttpInfo(id, personUpdateDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } diff --git a/mobile/openapi/lib/api/plugins_api.dart b/mobile/openapi/lib/api/plugins_api.dart index 5735fba379..40892b8a67 100644 --- a/mobile/openapi/lib/api/plugins_api.dart +++ b/mobile/openapi/lib/api/plugins_api.dart @@ -25,7 +25,7 @@ class PluginsApi { /// Parameters: /// /// * [String] id (required): - Future getPluginWithHttpInfo(String id,) async { + Future getPluginWithHttpInfo(String id, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/plugins/{id}' .replaceAll('{id}', id); @@ -48,6 +48,7 @@ class PluginsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -58,8 +59,8 @@ class PluginsApi { /// Parameters: /// /// * [String] id (required): - Future getPlugin(String id,) async { - final response = await getPluginWithHttpInfo(id,); + Future getPlugin(String id, { Future? abortTrigger, }) async { + final response = await getPluginWithHttpInfo(id, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -73,14 +74,146 @@ class PluginsApi { return null; } - /// List all plugin triggers + /// Retrieve plugin methods /// - /// Retrieve a list of all available plugin triggers. + /// Retrieve a list of plugin methods /// /// Note: This method returns the HTTP [Response]. - Future getPluginTriggersWithHttpInfo() async { + /// + /// Parameters: + /// + /// * [String] description: + /// + /// * [bool] enabled: + /// Whether the plugin method is enabled + /// + /// * [String] id: + /// Plugin method ID + /// + /// * [String] name: + /// + /// * [String] pluginName: + /// Plugin name + /// + /// * [String] pluginVersion: + /// Plugin version + /// + /// * [String] title: + /// + /// * [WorkflowTrigger] trigger: + /// Workflow trigger + /// + /// * [WorkflowType] type: + /// Workflow types + Future searchPluginMethodsWithHttpInfo({ String? description, bool? enabled, String? id, String? name, String? pluginName, String? pluginVersion, String? title, WorkflowTrigger? trigger, WorkflowType? type, Future? abortTrigger, }) async { // ignore: prefer_const_declarations - final apiPath = r'/plugins/triggers'; + final apiPath = r'/plugins/methods'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + if (description != null) { + queryParams.addAll(_queryParams('', 'description', description)); + } + if (enabled != null) { + queryParams.addAll(_queryParams('', 'enabled', enabled)); + } + if (id != null) { + queryParams.addAll(_queryParams('', 'id', id)); + } + if (name != null) { + queryParams.addAll(_queryParams('', 'name', name)); + } + if (pluginName != null) { + queryParams.addAll(_queryParams('', 'pluginName', pluginName)); + } + if (pluginVersion != null) { + queryParams.addAll(_queryParams('', 'pluginVersion', pluginVersion)); + } + if (title != null) { + queryParams.addAll(_queryParams('', 'title', title)); + } + if (trigger != null) { + queryParams.addAll(_queryParams('', 'trigger', trigger)); + } + if (type != null) { + queryParams.addAll(_queryParams('', 'type', type)); + } + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, + ); + } + + /// Retrieve plugin methods + /// + /// Retrieve a list of plugin methods + /// + /// Parameters: + /// + /// * [String] description: + /// + /// * [bool] enabled: + /// Whether the plugin method is enabled + /// + /// * [String] id: + /// Plugin method ID + /// + /// * [String] name: + /// + /// * [String] pluginName: + /// Plugin name + /// + /// * [String] pluginVersion: + /// Plugin version + /// + /// * [String] title: + /// + /// * [WorkflowTrigger] trigger: + /// Workflow trigger + /// + /// * [WorkflowType] type: + /// Workflow types + Future?> searchPluginMethods({ String? description, bool? enabled, String? id, String? name, String? pluginName, String? pluginVersion, String? title, WorkflowTrigger? trigger, WorkflowType? type, Future? abortTrigger, }) async { + final response = await searchPluginMethodsWithHttpInfo(description: description, enabled: enabled, id: id, name: name, pluginName: pluginName, pluginVersion: pluginVersion, title: title, trigger: trigger, type: type, abortTrigger: abortTrigger,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// Retrieve workflow templates + /// + /// Retrieve workflow templates provided by installed plugins + /// + /// Note: This method returns the HTTP [Response]. + Future searchPluginTemplatesWithHttpInfo({ Future? abortTrigger, }) async { + // ignore: prefer_const_declarations + final apiPath = r'/plugins/templates'; // ignore: prefer_final_locals Object? postBody; @@ -100,14 +233,15 @@ class PluginsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } - /// List all plugin triggers + /// Retrieve workflow templates /// - /// Retrieve a list of all available plugin triggers. - Future?> getPluginTriggers() async { - final response = await getPluginTriggersWithHttpInfo(); + /// Retrieve workflow templates provided by installed plugins + Future?> searchPluginTemplates({ Future? abortTrigger, }) async { + final response = await searchPluginTemplatesWithHttpInfo(abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -116,8 +250,8 @@ class PluginsApi { // FormatException when trying to decode an empty string. if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { final responseBody = await _decodeBodyBytes(response); - return (await apiClient.deserializeAsync(responseBody, 'List') as List) - .cast() + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() .toList(growable: false); } @@ -129,7 +263,23 @@ class PluginsApi { /// Retrieve a list of plugins available to the authenticated user. /// /// Note: This method returns the HTTP [Response]. - Future getPluginsWithHttpInfo() async { + /// + /// Parameters: + /// + /// * [String] description: + /// + /// * [bool] enabled: + /// Whether the plugin is enabled + /// + /// * [String] id: + /// Plugin ID + /// + /// * [String] name: + /// + /// * [String] title: + /// + /// * [String] version: + Future searchPluginsWithHttpInfo({ String? description, bool? enabled, String? id, String? name, String? title, String? version, Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/plugins'; @@ -140,6 +290,25 @@ class PluginsApi { final headerParams = {}; final formParams = {}; + if (description != null) { + queryParams.addAll(_queryParams('', 'description', description)); + } + if (enabled != null) { + queryParams.addAll(_queryParams('', 'enabled', enabled)); + } + if (id != null) { + queryParams.addAll(_queryParams('', 'id', id)); + } + if (name != null) { + queryParams.addAll(_queryParams('', 'name', name)); + } + if (title != null) { + queryParams.addAll(_queryParams('', 'title', title)); + } + if (version != null) { + queryParams.addAll(_queryParams('', 'version', version)); + } + const contentTypes = []; @@ -151,14 +320,31 @@ class PluginsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } /// List all plugins /// /// Retrieve a list of plugins available to the authenticated user. - Future?> getPlugins() async { - final response = await getPluginsWithHttpInfo(); + /// + /// Parameters: + /// + /// * [String] description: + /// + /// * [bool] enabled: + /// Whether the plugin is enabled + /// + /// * [String] id: + /// Plugin ID + /// + /// * [String] name: + /// + /// * [String] title: + /// + /// * [String] version: + Future?> searchPlugins({ String? description, bool? enabled, String? id, String? name, String? title, String? version, Future? abortTrigger, }) async { + final response = await searchPluginsWithHttpInfo(description: description, enabled: enabled, id: id, name: name, title: title, version: version, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } diff --git a/mobile/openapi/lib/api/queues_api.dart b/mobile/openapi/lib/api/queues_api.dart index 1312cb5952..39386c23f9 100644 --- a/mobile/openapi/lib/api/queues_api.dart +++ b/mobile/openapi/lib/api/queues_api.dart @@ -27,7 +27,7 @@ class QueuesApi { /// * [QueueName] name (required): /// /// * [QueueDeleteDto] queueDeleteDto (required): - Future emptyQueueWithHttpInfo(QueueName name, QueueDeleteDto queueDeleteDto,) async { + Future emptyQueueWithHttpInfo(QueueName name, QueueDeleteDto queueDeleteDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/queues/{name}/jobs' .replaceAll('{name}', name.toString()); @@ -50,6 +50,7 @@ class QueuesApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -62,8 +63,8 @@ class QueuesApi { /// * [QueueName] name (required): /// /// * [QueueDeleteDto] queueDeleteDto (required): - Future emptyQueue(QueueName name, QueueDeleteDto queueDeleteDto,) async { - final response = await emptyQueueWithHttpInfo(name, queueDeleteDto,); + Future emptyQueue(QueueName name, QueueDeleteDto queueDeleteDto, { Future? abortTrigger, }) async { + final response = await emptyQueueWithHttpInfo(name, queueDeleteDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -78,7 +79,7 @@ class QueuesApi { /// Parameters: /// /// * [QueueName] name (required): - Future getQueueWithHttpInfo(QueueName name,) async { + Future getQueueWithHttpInfo(QueueName name, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/queues/{name}' .replaceAll('{name}', name.toString()); @@ -101,6 +102,7 @@ class QueuesApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -111,8 +113,8 @@ class QueuesApi { /// Parameters: /// /// * [QueueName] name (required): - Future getQueue(QueueName name,) async { - final response = await getQueueWithHttpInfo(name,); + Future getQueue(QueueName name, { Future? abortTrigger, }) async { + final response = await getQueueWithHttpInfo(name, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -138,7 +140,7 @@ class QueuesApi { /// /// * [List] status: /// Filter jobs by status - Future getQueueJobsWithHttpInfo(QueueName name, { List? status, }) async { + Future getQueueJobsWithHttpInfo(QueueName name, { List? status, Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/queues/{name}/jobs' .replaceAll('{name}', name.toString()); @@ -165,6 +167,7 @@ class QueuesApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -178,8 +181,8 @@ class QueuesApi { /// /// * [List] status: /// Filter jobs by status - Future?> getQueueJobs(QueueName name, { List? status, }) async { - final response = await getQueueJobsWithHttpInfo(name, status: status, ); + Future?> getQueueJobs(QueueName name, { List? status, Future? abortTrigger, }) async { + final response = await getQueueJobsWithHttpInfo(name, status: status, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -201,7 +204,7 @@ class QueuesApi { /// Retrieves a list of queues. /// /// Note: This method returns the HTTP [Response]. - Future getQueuesWithHttpInfo() async { + Future getQueuesWithHttpInfo({ Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/queues'; @@ -223,14 +226,15 @@ class QueuesApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } /// List all queues /// /// Retrieves a list of queues. - Future?> getQueues() async { - final response = await getQueuesWithHttpInfo(); + Future?> getQueues({ Future? abortTrigger, }) async { + final response = await getQueuesWithHttpInfo(abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -258,7 +262,7 @@ class QueuesApi { /// * [QueueName] name (required): /// /// * [QueueUpdateDto] queueUpdateDto (required): - Future updateQueueWithHttpInfo(QueueName name, QueueUpdateDto queueUpdateDto,) async { + Future updateQueueWithHttpInfo(QueueName name, QueueUpdateDto queueUpdateDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/queues/{name}' .replaceAll('{name}', name.toString()); @@ -281,6 +285,7 @@ class QueuesApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -293,8 +298,8 @@ class QueuesApi { /// * [QueueName] name (required): /// /// * [QueueUpdateDto] queueUpdateDto (required): - Future updateQueue(QueueName name, QueueUpdateDto queueUpdateDto,) async { - final response = await updateQueueWithHttpInfo(name, queueUpdateDto,); + Future updateQueue(QueueName name, QueueUpdateDto queueUpdateDto, { Future? abortTrigger, }) async { + final response = await updateQueueWithHttpInfo(name, queueUpdateDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } diff --git a/mobile/openapi/lib/api/search_api.dart b/mobile/openapi/lib/api/search_api.dart index 6f8a4df902..0118cabdba 100644 --- a/mobile/openapi/lib/api/search_api.dart +++ b/mobile/openapi/lib/api/search_api.dart @@ -21,7 +21,7 @@ class SearchApi { /// Retrieve a list of assets with each asset belonging to a different city. This endpoint is used on the places pages to show a single thumbnail for each city the user has assets in. /// /// Note: This method returns the HTTP [Response]. - Future getAssetsByCityWithHttpInfo() async { + Future getAssetsByCityWithHttpInfo({ Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/search/cities'; @@ -43,14 +43,15 @@ class SearchApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } /// Retrieve assets by city /// /// Retrieve a list of assets with each asset belonging to a different city. This endpoint is used on the places pages to show a single thumbnail for each city the user has assets in. - Future?> getAssetsByCity() async { - final response = await getAssetsByCityWithHttpInfo(); + Future?> getAssetsByCity({ Future? abortTrigger, }) async { + final response = await getAssetsByCityWithHttpInfo(abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -72,7 +73,7 @@ class SearchApi { /// Retrieve data for the explore section, such as popular people and places. /// /// Note: This method returns the HTTP [Response]. - Future getExploreDataWithHttpInfo() async { + Future getExploreDataWithHttpInfo({ Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/search/explore'; @@ -94,14 +95,15 @@ class SearchApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } /// Retrieve explore data /// /// Retrieve data for the explore section, such as popular people and places. - Future?> getExploreData() async { - final response = await getExploreDataWithHttpInfo(); + Future?> getExploreData({ Future? abortTrigger, }) async { + final response = await getExploreDataWithHttpInfo(abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -145,7 +147,7 @@ class SearchApi { /// /// * [String] state: /// Filter by state/province - Future getSearchSuggestionsWithHttpInfo(SearchSuggestionType type, { String? country, bool? includeNull, String? lensModel, String? make, String? model, String? state, }) async { + Future getSearchSuggestionsWithHttpInfo(SearchSuggestionType type, { String? country, bool? includeNull, String? lensModel, String? make, String? model, String? state, Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/search/suggestions'; @@ -187,6 +189,7 @@ class SearchApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -215,8 +218,8 @@ class SearchApi { /// /// * [String] state: /// Filter by state/province - Future?> getSearchSuggestions(SearchSuggestionType type, { String? country, bool? includeNull, String? lensModel, String? make, String? model, String? state, }) async { - final response = await getSearchSuggestionsWithHttpInfo(type, country: country, includeNull: includeNull, lensModel: lensModel, make: make, model: model, state: state, ); + Future?> getSearchSuggestions(SearchSuggestionType type, { String? country, bool? includeNull, String? lensModel, String? make, String? model, String? state, Future? abortTrigger, }) async { + final response = await getSearchSuggestionsWithHttpInfo(type, country: country, includeNull: includeNull, lensModel: lensModel, make: make, model: model, state: state, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -242,7 +245,7 @@ class SearchApi { /// Parameters: /// /// * [StatisticsSearchDto] statisticsSearchDto (required): - Future searchAssetStatisticsWithHttpInfo(StatisticsSearchDto statisticsSearchDto,) async { + Future searchAssetStatisticsWithHttpInfo(StatisticsSearchDto statisticsSearchDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/search/statistics'; @@ -264,6 +267,7 @@ class SearchApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -274,8 +278,8 @@ class SearchApi { /// Parameters: /// /// * [StatisticsSearchDto] statisticsSearchDto (required): - Future searchAssetStatistics(StatisticsSearchDto statisticsSearchDto,) async { - final response = await searchAssetStatisticsWithHttpInfo(statisticsSearchDto,); + Future searchAssetStatistics(StatisticsSearchDto statisticsSearchDto, { Future? abortTrigger, }) async { + final response = await searchAssetStatisticsWithHttpInfo(statisticsSearchDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -298,7 +302,7 @@ class SearchApi { /// Parameters: /// /// * [MetadataSearchDto] metadataSearchDto (required): - Future searchAssetsWithHttpInfo(MetadataSearchDto metadataSearchDto,) async { + Future searchAssetsWithHttpInfo(MetadataSearchDto metadataSearchDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/search/metadata'; @@ -320,6 +324,7 @@ class SearchApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -330,8 +335,8 @@ class SearchApi { /// Parameters: /// /// * [MetadataSearchDto] metadataSearchDto (required): - Future searchAssets(MetadataSearchDto metadataSearchDto,) async { - final response = await searchAssetsWithHttpInfo(metadataSearchDto,); + Future searchAssets(MetadataSearchDto metadataSearchDto, { Future? abortTrigger, }) async { + final response = await searchAssetsWithHttpInfo(metadataSearchDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -443,7 +448,7 @@ class SearchApi { /// /// * [bool] withExif: /// Include EXIF data in response - Future searchLargeAssetsWithHttpInfo({ List? albumIds, String? city, String? country, DateTime? createdAfter, DateTime? createdBefore, bool? isEncoded, bool? isFavorite, bool? isMotion, bool? isNotInAlbum, bool? isOffline, String? lensModel, String? libraryId, String? make, int? minFileSize, String? model, String? ocr, List? personIds, int? rating, int? size, String? state, List? tagIds, DateTime? takenAfter, DateTime? takenBefore, DateTime? trashedAfter, DateTime? trashedBefore, AssetTypeEnum? type, DateTime? updatedAfter, DateTime? updatedBefore, AssetVisibility? visibility, bool? withDeleted, bool? withExif, }) async { + Future searchLargeAssetsWithHttpInfo({ List? albumIds, String? city, String? country, DateTime? createdAfter, DateTime? createdBefore, bool? isEncoded, bool? isFavorite, bool? isMotion, bool? isNotInAlbum, bool? isOffline, String? lensModel, String? libraryId, String? make, int? minFileSize, String? model, String? ocr, List? personIds, int? rating, int? size, String? state, List? tagIds, DateTime? takenAfter, DateTime? takenBefore, DateTime? trashedAfter, DateTime? trashedBefore, AssetTypeEnum? type, DateTime? updatedAfter, DateTime? updatedBefore, AssetVisibility? visibility, bool? withDeleted, bool? withExif, Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/search/large-assets'; @@ -559,6 +564,7 @@ class SearchApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -658,8 +664,8 @@ class SearchApi { /// /// * [bool] withExif: /// Include EXIF data in response - Future?> searchLargeAssets({ List? albumIds, String? city, String? country, DateTime? createdAfter, DateTime? createdBefore, bool? isEncoded, bool? isFavorite, bool? isMotion, bool? isNotInAlbum, bool? isOffline, String? lensModel, String? libraryId, String? make, int? minFileSize, String? model, String? ocr, List? personIds, int? rating, int? size, String? state, List? tagIds, DateTime? takenAfter, DateTime? takenBefore, DateTime? trashedAfter, DateTime? trashedBefore, AssetTypeEnum? type, DateTime? updatedAfter, DateTime? updatedBefore, AssetVisibility? visibility, bool? withDeleted, bool? withExif, }) async { - final response = await searchLargeAssetsWithHttpInfo( albumIds: albumIds, city: city, country: country, createdAfter: createdAfter, createdBefore: createdBefore, isEncoded: isEncoded, isFavorite: isFavorite, isMotion: isMotion, isNotInAlbum: isNotInAlbum, isOffline: isOffline, lensModel: lensModel, libraryId: libraryId, make: make, minFileSize: minFileSize, model: model, ocr: ocr, personIds: personIds, rating: rating, size: size, state: state, tagIds: tagIds, takenAfter: takenAfter, takenBefore: takenBefore, trashedAfter: trashedAfter, trashedBefore: trashedBefore, type: type, updatedAfter: updatedAfter, updatedBefore: updatedBefore, visibility: visibility, withDeleted: withDeleted, withExif: withExif, ); + Future?> searchLargeAssets({ List? albumIds, String? city, String? country, DateTime? createdAfter, DateTime? createdBefore, bool? isEncoded, bool? isFavorite, bool? isMotion, bool? isNotInAlbum, bool? isOffline, String? lensModel, String? libraryId, String? make, int? minFileSize, String? model, String? ocr, List? personIds, int? rating, int? size, String? state, List? tagIds, DateTime? takenAfter, DateTime? takenBefore, DateTime? trashedAfter, DateTime? trashedBefore, AssetTypeEnum? type, DateTime? updatedAfter, DateTime? updatedBefore, AssetVisibility? visibility, bool? withDeleted, bool? withExif, Future? abortTrigger, }) async { + final response = await searchLargeAssetsWithHttpInfo(albumIds: albumIds, city: city, country: country, createdAfter: createdAfter, createdBefore: createdBefore, isEncoded: isEncoded, isFavorite: isFavorite, isMotion: isMotion, isNotInAlbum: isNotInAlbum, isOffline: isOffline, lensModel: lensModel, libraryId: libraryId, make: make, minFileSize: minFileSize, model: model, ocr: ocr, personIds: personIds, rating: rating, size: size, state: state, tagIds: tagIds, takenAfter: takenAfter, takenBefore: takenBefore, trashedAfter: trashedAfter, trashedBefore: trashedBefore, type: type, updatedAfter: updatedAfter, updatedBefore: updatedBefore, visibility: visibility, withDeleted: withDeleted, withExif: withExif, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -689,7 +695,7 @@ class SearchApi { /// /// * [bool] withHidden: /// Include hidden people - Future searchPersonWithHttpInfo(String name, { bool? withHidden, }) async { + Future searchPersonWithHttpInfo(String name, { bool? withHidden, Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/search/person'; @@ -716,6 +722,7 @@ class SearchApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -730,8 +737,8 @@ class SearchApi { /// /// * [bool] withHidden: /// Include hidden people - Future?> searchPerson(String name, { bool? withHidden, }) async { - final response = await searchPersonWithHttpInfo(name, withHidden: withHidden, ); + Future?> searchPerson(String name, { bool? withHidden, Future? abortTrigger, }) async { + final response = await searchPersonWithHttpInfo(name, withHidden: withHidden, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -758,7 +765,7 @@ class SearchApi { /// /// * [String] name (required): /// Place name to search for - Future searchPlacesWithHttpInfo(String name,) async { + Future searchPlacesWithHttpInfo(String name, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/search/places'; @@ -782,6 +789,7 @@ class SearchApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -793,8 +801,8 @@ class SearchApi { /// /// * [String] name (required): /// Place name to search for - Future?> searchPlaces(String name,) async { - final response = await searchPlacesWithHttpInfo(name,); + Future?> searchPlaces(String name, { Future? abortTrigger, }) async { + final response = await searchPlacesWithHttpInfo(name, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -820,7 +828,7 @@ class SearchApi { /// Parameters: /// /// * [RandomSearchDto] randomSearchDto (required): - Future searchRandomWithHttpInfo(RandomSearchDto randomSearchDto,) async { + Future searchRandomWithHttpInfo(RandomSearchDto randomSearchDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/search/random'; @@ -842,6 +850,7 @@ class SearchApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -852,8 +861,8 @@ class SearchApi { /// Parameters: /// /// * [RandomSearchDto] randomSearchDto (required): - Future?> searchRandom(RandomSearchDto randomSearchDto,) async { - final response = await searchRandomWithHttpInfo(randomSearchDto,); + Future?> searchRandom(RandomSearchDto randomSearchDto, { Future? abortTrigger, }) async { + final response = await searchRandomWithHttpInfo(randomSearchDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -879,7 +888,7 @@ class SearchApi { /// Parameters: /// /// * [SmartSearchDto] smartSearchDto (required): - Future searchSmartWithHttpInfo(SmartSearchDto smartSearchDto,) async { + Future searchSmartWithHttpInfo(SmartSearchDto smartSearchDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/search/smart'; @@ -901,6 +910,7 @@ class SearchApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -911,8 +921,8 @@ class SearchApi { /// Parameters: /// /// * [SmartSearchDto] smartSearchDto (required): - Future searchSmart(SmartSearchDto smartSearchDto,) async { - final response = await searchSmartWithHttpInfo(smartSearchDto,); + Future searchSmart(SmartSearchDto smartSearchDto, { Future? abortTrigger, }) async { + final response = await searchSmartWithHttpInfo(smartSearchDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } diff --git a/mobile/openapi/lib/api/server_api.dart b/mobile/openapi/lib/api/server_api.dart index dd38ade167..1a46a86188 100644 --- a/mobile/openapi/lib/api/server_api.dart +++ b/mobile/openapi/lib/api/server_api.dart @@ -21,7 +21,7 @@ class ServerApi { /// Delete the currently set server product key. /// /// Note: This method returns the HTTP [Response]. - Future deleteServerLicenseWithHttpInfo() async { + Future deleteServerLicenseWithHttpInfo({ Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/server/license'; @@ -43,14 +43,15 @@ class ServerApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } /// Delete server product key /// /// Delete the currently set server product key. - Future deleteServerLicense() async { - final response = await deleteServerLicenseWithHttpInfo(); + Future deleteServerLicense({ Future? abortTrigger, }) async { + final response = await deleteServerLicenseWithHttpInfo(abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -61,7 +62,7 @@ class ServerApi { /// Retrieve a list of information about the server. /// /// Note: This method returns the HTTP [Response]. - Future getAboutInfoWithHttpInfo() async { + Future getAboutInfoWithHttpInfo({ Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/server/about'; @@ -83,14 +84,15 @@ class ServerApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } /// Get server information /// /// Retrieve a list of information about the server. - Future getAboutInfo() async { - final response = await getAboutInfoWithHttpInfo(); + Future getAboutInfo({ Future? abortTrigger, }) async { + final response = await getAboutInfoWithHttpInfo(abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -109,7 +111,7 @@ class ServerApi { /// Retrieve links to the APKs for the current server version. /// /// Note: This method returns the HTTP [Response]. - Future getApkLinksWithHttpInfo() async { + Future getApkLinksWithHttpInfo({ Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/server/apk-links'; @@ -131,14 +133,15 @@ class ServerApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } /// Get APK links /// /// Retrieve links to the APKs for the current server version. - Future getApkLinks() async { - final response = await getApkLinksWithHttpInfo(); + Future getApkLinks({ Future? abortTrigger, }) async { + final response = await getApkLinksWithHttpInfo(abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -157,7 +160,7 @@ class ServerApi { /// Retrieve the current server configuration. /// /// Note: This method returns the HTTP [Response]. - Future getServerConfigWithHttpInfo() async { + Future getServerConfigWithHttpInfo({ Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/server/config'; @@ -179,14 +182,15 @@ class ServerApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } /// Get config /// /// Retrieve the current server configuration. - Future getServerConfig() async { - final response = await getServerConfigWithHttpInfo(); + Future getServerConfig({ Future? abortTrigger, }) async { + final response = await getServerConfigWithHttpInfo(abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -205,7 +209,7 @@ class ServerApi { /// Retrieve available features supported by this server. /// /// Note: This method returns the HTTP [Response]. - Future getServerFeaturesWithHttpInfo() async { + Future getServerFeaturesWithHttpInfo({ Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/server/features'; @@ -227,14 +231,15 @@ class ServerApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } /// Get features /// /// Retrieve available features supported by this server. - Future getServerFeatures() async { - final response = await getServerFeaturesWithHttpInfo(); + Future getServerFeatures({ Future? abortTrigger, }) async { + final response = await getServerFeaturesWithHttpInfo(abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -253,7 +258,7 @@ class ServerApi { /// Retrieve information about whether the server currently has a product key registered. /// /// Note: This method returns the HTTP [Response]. - Future getServerLicenseWithHttpInfo() async { + Future getServerLicenseWithHttpInfo({ Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/server/license'; @@ -275,14 +280,15 @@ class ServerApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } /// Get product key /// /// Retrieve information about whether the server currently has a product key registered. - Future getServerLicense() async { - final response = await getServerLicenseWithHttpInfo(); + Future getServerLicense({ Future? abortTrigger, }) async { + final response = await getServerLicenseWithHttpInfo(abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -301,7 +307,7 @@ class ServerApi { /// Retrieve statistics about the entire Immich instance such as asset counts. /// /// Note: This method returns the HTTP [Response]. - Future getServerStatisticsWithHttpInfo() async { + Future getServerStatisticsWithHttpInfo({ Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/server/statistics'; @@ -323,14 +329,15 @@ class ServerApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } /// Get statistics /// /// Retrieve statistics about the entire Immich instance such as asset counts. - Future getServerStatistics() async { - final response = await getServerStatisticsWithHttpInfo(); + Future getServerStatistics({ Future? abortTrigger, }) async { + final response = await getServerStatisticsWithHttpInfo(abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -349,7 +356,7 @@ class ServerApi { /// Retrieve the current server version in semantic versioning (semver) format. /// /// Note: This method returns the HTTP [Response]. - Future getServerVersionWithHttpInfo() async { + Future getServerVersionWithHttpInfo({ Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/server/version'; @@ -371,14 +378,15 @@ class ServerApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } /// Get server version /// /// Retrieve the current server version in semantic versioning (semver) format. - Future getServerVersion() async { - final response = await getServerVersionWithHttpInfo(); + Future getServerVersion({ Future? abortTrigger, }) async { + final response = await getServerVersionWithHttpInfo(abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -397,7 +405,7 @@ class ServerApi { /// Retrieve the current storage utilization information of the server. /// /// Note: This method returns the HTTP [Response]. - Future getStorageWithHttpInfo() async { + Future getStorageWithHttpInfo({ Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/server/storage'; @@ -419,14 +427,15 @@ class ServerApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } /// Get storage /// /// Retrieve the current storage utilization information of the server. - Future getStorage() async { - final response = await getStorageWithHttpInfo(); + Future getStorage({ Future? abortTrigger, }) async { + final response = await getStorageWithHttpInfo(abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -445,7 +454,7 @@ class ServerApi { /// Retrieve all media types supported by the server. /// /// Note: This method returns the HTTP [Response]. - Future getSupportedMediaTypesWithHttpInfo() async { + Future getSupportedMediaTypesWithHttpInfo({ Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/server/media-types'; @@ -467,14 +476,15 @@ class ServerApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } /// Get supported media types /// /// Retrieve all media types supported by the server. - Future getSupportedMediaTypes() async { - final response = await getSupportedMediaTypesWithHttpInfo(); + Future getSupportedMediaTypes({ Future? abortTrigger, }) async { + final response = await getSupportedMediaTypesWithHttpInfo(abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -493,7 +503,7 @@ class ServerApi { /// Retrieve information about the last time the version check ran. /// /// Note: This method returns the HTTP [Response]. - Future getVersionCheckWithHttpInfo() async { + Future getVersionCheckWithHttpInfo({ Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/server/version-check'; @@ -515,14 +525,15 @@ class ServerApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } /// Get version check status /// /// Retrieve information about the last time the version check ran. - Future getVersionCheck() async { - final response = await getVersionCheckWithHttpInfo(); + Future getVersionCheck({ Future? abortTrigger, }) async { + final response = await getVersionCheckWithHttpInfo(abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -541,7 +552,7 @@ class ServerApi { /// Retrieve a list of past versions the server has been on. /// /// Note: This method returns the HTTP [Response]. - Future getVersionHistoryWithHttpInfo() async { + Future getVersionHistoryWithHttpInfo({ Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/server/version-history'; @@ -563,14 +574,15 @@ class ServerApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } /// Get version history /// /// Retrieve a list of past versions the server has been on. - Future?> getVersionHistory() async { - final response = await getVersionHistoryWithHttpInfo(); + Future?> getVersionHistory({ Future? abortTrigger, }) async { + final response = await getVersionHistoryWithHttpInfo(abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -592,7 +604,7 @@ class ServerApi { /// Pong /// /// Note: This method returns the HTTP [Response]. - Future pingServerWithHttpInfo() async { + Future pingServerWithHttpInfo({ Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/server/ping'; @@ -614,14 +626,15 @@ class ServerApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } /// Ping /// /// Pong - Future pingServer() async { - final response = await pingServerWithHttpInfo(); + Future pingServer({ Future? abortTrigger, }) async { + final response = await pingServerWithHttpInfo(abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -644,7 +657,7 @@ class ServerApi { /// Parameters: /// /// * [LicenseKeyDto] licenseKeyDto (required): - Future setServerLicenseWithHttpInfo(LicenseKeyDto licenseKeyDto,) async { + Future setServerLicenseWithHttpInfo(LicenseKeyDto licenseKeyDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/server/license'; @@ -666,6 +679,7 @@ class ServerApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -676,8 +690,8 @@ class ServerApi { /// Parameters: /// /// * [LicenseKeyDto] licenseKeyDto (required): - Future setServerLicense(LicenseKeyDto licenseKeyDto,) async { - final response = await setServerLicenseWithHttpInfo(licenseKeyDto,); + Future setServerLicense(LicenseKeyDto licenseKeyDto, { Future? abortTrigger, }) async { + final response = await setServerLicenseWithHttpInfo(licenseKeyDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } diff --git a/mobile/openapi/lib/api/sessions_api.dart b/mobile/openapi/lib/api/sessions_api.dart index da508059bc..fdd6c09266 100644 --- a/mobile/openapi/lib/api/sessions_api.dart +++ b/mobile/openapi/lib/api/sessions_api.dart @@ -25,7 +25,7 @@ class SessionsApi { /// Parameters: /// /// * [SessionCreateDto] sessionCreateDto (required): - Future createSessionWithHttpInfo(SessionCreateDto sessionCreateDto,) async { + Future createSessionWithHttpInfo(SessionCreateDto sessionCreateDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/sessions'; @@ -47,6 +47,7 @@ class SessionsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -57,8 +58,8 @@ class SessionsApi { /// Parameters: /// /// * [SessionCreateDto] sessionCreateDto (required): - Future createSession(SessionCreateDto sessionCreateDto,) async { - final response = await createSessionWithHttpInfo(sessionCreateDto,); + Future createSession(SessionCreateDto sessionCreateDto, { Future? abortTrigger, }) async { + final response = await createSessionWithHttpInfo(sessionCreateDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -77,7 +78,7 @@ class SessionsApi { /// Delete all sessions for the user. This will not delete the current session. /// /// Note: This method returns the HTTP [Response]. - Future deleteAllSessionsWithHttpInfo() async { + Future deleteAllSessionsWithHttpInfo({ Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/sessions'; @@ -99,14 +100,15 @@ class SessionsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } /// Delete all sessions /// /// Delete all sessions for the user. This will not delete the current session. - Future deleteAllSessions() async { - final response = await deleteAllSessionsWithHttpInfo(); + Future deleteAllSessions({ Future? abortTrigger, }) async { + final response = await deleteAllSessionsWithHttpInfo(abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -121,7 +123,7 @@ class SessionsApi { /// Parameters: /// /// * [String] id (required): - Future deleteSessionWithHttpInfo(String id,) async { + Future deleteSessionWithHttpInfo(String id, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/sessions/{id}' .replaceAll('{id}', id); @@ -144,6 +146,7 @@ class SessionsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -154,8 +157,8 @@ class SessionsApi { /// Parameters: /// /// * [String] id (required): - Future deleteSession(String id,) async { - final response = await deleteSessionWithHttpInfo(id,); + Future deleteSession(String id, { Future? abortTrigger, }) async { + final response = await deleteSessionWithHttpInfo(id, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -166,7 +169,7 @@ class SessionsApi { /// Retrieve a list of sessions for the user. /// /// Note: This method returns the HTTP [Response]. - Future getSessionsWithHttpInfo() async { + Future getSessionsWithHttpInfo({ Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/sessions'; @@ -188,14 +191,15 @@ class SessionsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } /// Retrieve sessions /// /// Retrieve a list of sessions for the user. - Future?> getSessions() async { - final response = await getSessionsWithHttpInfo(); + Future?> getSessions({ Future? abortTrigger, }) async { + final response = await getSessionsWithHttpInfo(abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -221,7 +225,7 @@ class SessionsApi { /// Parameters: /// /// * [String] id (required): - Future lockSessionWithHttpInfo(String id,) async { + Future lockSessionWithHttpInfo(String id, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/sessions/{id}/lock' .replaceAll('{id}', id); @@ -244,6 +248,7 @@ class SessionsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -254,8 +259,8 @@ class SessionsApi { /// Parameters: /// /// * [String] id (required): - Future lockSession(String id,) async { - final response = await lockSessionWithHttpInfo(id,); + Future lockSession(String id, { Future? abortTrigger, }) async { + final response = await lockSessionWithHttpInfo(id, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -272,7 +277,7 @@ class SessionsApi { /// * [String] id (required): /// /// * [SessionUpdateDto] sessionUpdateDto (required): - Future updateSessionWithHttpInfo(String id, SessionUpdateDto sessionUpdateDto,) async { + Future updateSessionWithHttpInfo(String id, SessionUpdateDto sessionUpdateDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/sessions/{id}' .replaceAll('{id}', id); @@ -295,6 +300,7 @@ class SessionsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -307,8 +313,8 @@ class SessionsApi { /// * [String] id (required): /// /// * [SessionUpdateDto] sessionUpdateDto (required): - Future updateSession(String id, SessionUpdateDto sessionUpdateDto,) async { - final response = await updateSessionWithHttpInfo(id, sessionUpdateDto,); + Future updateSession(String id, SessionUpdateDto sessionUpdateDto, { Future? abortTrigger, }) async { + final response = await updateSessionWithHttpInfo(id, sessionUpdateDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } diff --git a/mobile/openapi/lib/api/shared_links_api.dart b/mobile/openapi/lib/api/shared_links_api.dart index 4750442287..5bd548d7d2 100644 --- a/mobile/openapi/lib/api/shared_links_api.dart +++ b/mobile/openapi/lib/api/shared_links_api.dart @@ -27,7 +27,7 @@ class SharedLinksApi { /// * [String] id (required): /// /// * [AssetIdsDto] assetIdsDto (required): - Future addSharedLinkAssetsWithHttpInfo(String id, AssetIdsDto assetIdsDto,) async { + Future addSharedLinkAssetsWithHttpInfo(String id, AssetIdsDto assetIdsDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/shared-links/{id}/assets' .replaceAll('{id}', id); @@ -50,6 +50,7 @@ class SharedLinksApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -62,8 +63,8 @@ class SharedLinksApi { /// * [String] id (required): /// /// * [AssetIdsDto] assetIdsDto (required): - Future?> addSharedLinkAssets(String id, AssetIdsDto assetIdsDto,) async { - final response = await addSharedLinkAssetsWithHttpInfo(id, assetIdsDto,); + Future?> addSharedLinkAssets(String id, AssetIdsDto assetIdsDto, { Future? abortTrigger, }) async { + final response = await addSharedLinkAssetsWithHttpInfo(id, assetIdsDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -89,7 +90,7 @@ class SharedLinksApi { /// Parameters: /// /// * [SharedLinkCreateDto] sharedLinkCreateDto (required): - Future createSharedLinkWithHttpInfo(SharedLinkCreateDto sharedLinkCreateDto,) async { + Future createSharedLinkWithHttpInfo(SharedLinkCreateDto sharedLinkCreateDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/shared-links'; @@ -111,6 +112,7 @@ class SharedLinksApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -121,8 +123,8 @@ class SharedLinksApi { /// Parameters: /// /// * [SharedLinkCreateDto] sharedLinkCreateDto (required): - Future createSharedLink(SharedLinkCreateDto sharedLinkCreateDto,) async { - final response = await createSharedLinkWithHttpInfo(sharedLinkCreateDto,); + Future createSharedLink(SharedLinkCreateDto sharedLinkCreateDto, { Future? abortTrigger, }) async { + final response = await createSharedLinkWithHttpInfo(sharedLinkCreateDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -149,7 +151,7 @@ class SharedLinksApi { /// /// * [String] id: /// Filter by shared link ID - Future getAllSharedLinksWithHttpInfo({ String? albumId, String? id, }) async { + Future getAllSharedLinksWithHttpInfo({ String? albumId, String? id, Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/shared-links'; @@ -178,6 +180,7 @@ class SharedLinksApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -192,8 +195,8 @@ class SharedLinksApi { /// /// * [String] id: /// Filter by shared link ID - Future?> getAllSharedLinks({ String? albumId, String? id, }) async { - final response = await getAllSharedLinksWithHttpInfo( albumId: albumId, id: id, ); + Future?> getAllSharedLinks({ String? albumId, String? id, Future? abortTrigger, }) async { + final response = await getAllSharedLinksWithHttpInfo(albumId: albumId, id: id, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -221,7 +224,7 @@ class SharedLinksApi { /// * [String] key: /// /// * [String] slug: - Future getMySharedLinkWithHttpInfo({ String? key, String? slug, }) async { + Future getMySharedLinkWithHttpInfo({ String? key, String? slug, Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/shared-links/me'; @@ -250,6 +253,7 @@ class SharedLinksApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -262,8 +266,8 @@ class SharedLinksApi { /// * [String] key: /// /// * [String] slug: - Future getMySharedLink({ String? key, String? slug, }) async { - final response = await getMySharedLinkWithHttpInfo( key: key, slug: slug, ); + Future getMySharedLink({ String? key, String? slug, Future? abortTrigger, }) async { + final response = await getMySharedLinkWithHttpInfo(key: key, slug: slug, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -286,7 +290,7 @@ class SharedLinksApi { /// Parameters: /// /// * [String] id (required): - Future getSharedLinkByIdWithHttpInfo(String id,) async { + Future getSharedLinkByIdWithHttpInfo(String id, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/shared-links/{id}' .replaceAll('{id}', id); @@ -309,6 +313,7 @@ class SharedLinksApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -319,8 +324,8 @@ class SharedLinksApi { /// Parameters: /// /// * [String] id (required): - Future getSharedLinkById(String id,) async { - final response = await getSharedLinkByIdWithHttpInfo(id,); + Future getSharedLinkById(String id, { Future? abortTrigger, }) async { + final response = await getSharedLinkByIdWithHttpInfo(id, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -343,7 +348,7 @@ class SharedLinksApi { /// Parameters: /// /// * [String] id (required): - Future removeSharedLinkWithHttpInfo(String id,) async { + Future removeSharedLinkWithHttpInfo(String id, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/shared-links/{id}' .replaceAll('{id}', id); @@ -366,6 +371,7 @@ class SharedLinksApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -376,8 +382,8 @@ class SharedLinksApi { /// Parameters: /// /// * [String] id (required): - Future removeSharedLink(String id,) async { - final response = await removeSharedLinkWithHttpInfo(id,); + Future removeSharedLink(String id, { Future? abortTrigger, }) async { + final response = await removeSharedLinkWithHttpInfo(id, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -394,7 +400,7 @@ class SharedLinksApi { /// * [String] id (required): /// /// * [AssetIdsDto] assetIdsDto (required): - Future removeSharedLinkAssetsWithHttpInfo(String id, AssetIdsDto assetIdsDto,) async { + Future removeSharedLinkAssetsWithHttpInfo(String id, AssetIdsDto assetIdsDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/shared-links/{id}/assets' .replaceAll('{id}', id); @@ -417,6 +423,7 @@ class SharedLinksApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -429,8 +436,8 @@ class SharedLinksApi { /// * [String] id (required): /// /// * [AssetIdsDto] assetIdsDto (required): - Future?> removeSharedLinkAssets(String id, AssetIdsDto assetIdsDto,) async { - final response = await removeSharedLinkAssetsWithHttpInfo(id, assetIdsDto,); + Future?> removeSharedLinkAssets(String id, AssetIdsDto assetIdsDto, { Future? abortTrigger, }) async { + final response = await removeSharedLinkAssetsWithHttpInfo(id, assetIdsDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -460,7 +467,7 @@ class SharedLinksApi { /// * [String] key: /// /// * [String] slug: - Future sharedLinkLoginWithHttpInfo(SharedLinkLoginDto sharedLinkLoginDto, { String? key, String? slug, }) async { + Future sharedLinkLoginWithHttpInfo(SharedLinkLoginDto sharedLinkLoginDto, { String? key, String? slug, Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/shared-links/login'; @@ -489,6 +496,7 @@ class SharedLinksApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -503,8 +511,8 @@ class SharedLinksApi { /// * [String] key: /// /// * [String] slug: - Future sharedLinkLogin(SharedLinkLoginDto sharedLinkLoginDto, { String? key, String? slug, }) async { - final response = await sharedLinkLoginWithHttpInfo(sharedLinkLoginDto, key: key, slug: slug, ); + Future sharedLinkLogin(SharedLinkLoginDto sharedLinkLoginDto, { String? key, String? slug, Future? abortTrigger, }) async { + final response = await sharedLinkLoginWithHttpInfo(sharedLinkLoginDto, key: key, slug: slug, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -529,7 +537,7 @@ class SharedLinksApi { /// * [String] id (required): /// /// * [SharedLinkEditDto] sharedLinkEditDto (required): - Future updateSharedLinkWithHttpInfo(String id, SharedLinkEditDto sharedLinkEditDto,) async { + Future updateSharedLinkWithHttpInfo(String id, SharedLinkEditDto sharedLinkEditDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/shared-links/{id}' .replaceAll('{id}', id); @@ -552,6 +560,7 @@ class SharedLinksApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -564,8 +573,8 @@ class SharedLinksApi { /// * [String] id (required): /// /// * [SharedLinkEditDto] sharedLinkEditDto (required): - Future updateSharedLink(String id, SharedLinkEditDto sharedLinkEditDto,) async { - final response = await updateSharedLinkWithHttpInfo(id, sharedLinkEditDto,); + Future updateSharedLink(String id, SharedLinkEditDto sharedLinkEditDto, { Future? abortTrigger, }) async { + final response = await updateSharedLinkWithHttpInfo(id, sharedLinkEditDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } diff --git a/mobile/openapi/lib/api/stacks_api.dart b/mobile/openapi/lib/api/stacks_api.dart index a691af2a7d..a99ebe0600 100644 --- a/mobile/openapi/lib/api/stacks_api.dart +++ b/mobile/openapi/lib/api/stacks_api.dart @@ -25,7 +25,7 @@ class StacksApi { /// Parameters: /// /// * [StackCreateDto] stackCreateDto (required): - Future createStackWithHttpInfo(StackCreateDto stackCreateDto,) async { + Future createStackWithHttpInfo(StackCreateDto stackCreateDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/stacks'; @@ -47,6 +47,7 @@ class StacksApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -57,8 +58,8 @@ class StacksApi { /// Parameters: /// /// * [StackCreateDto] stackCreateDto (required): - Future createStack(StackCreateDto stackCreateDto,) async { - final response = await createStackWithHttpInfo(stackCreateDto,); + Future createStack(StackCreateDto stackCreateDto, { Future? abortTrigger, }) async { + final response = await createStackWithHttpInfo(stackCreateDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -81,7 +82,7 @@ class StacksApi { /// Parameters: /// /// * [String] id (required): - Future deleteStackWithHttpInfo(String id,) async { + Future deleteStackWithHttpInfo(String id, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/stacks/{id}' .replaceAll('{id}', id); @@ -104,6 +105,7 @@ class StacksApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -114,8 +116,8 @@ class StacksApi { /// Parameters: /// /// * [String] id (required): - Future deleteStack(String id,) async { - final response = await deleteStackWithHttpInfo(id,); + Future deleteStack(String id, { Future? abortTrigger, }) async { + final response = await deleteStackWithHttpInfo(id, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -130,7 +132,7 @@ class StacksApi { /// Parameters: /// /// * [BulkIdsDto] bulkIdsDto (required): - Future deleteStacksWithHttpInfo(BulkIdsDto bulkIdsDto,) async { + Future deleteStacksWithHttpInfo(BulkIdsDto bulkIdsDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/stacks'; @@ -152,6 +154,7 @@ class StacksApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -162,8 +165,8 @@ class StacksApi { /// Parameters: /// /// * [BulkIdsDto] bulkIdsDto (required): - Future deleteStacks(BulkIdsDto bulkIdsDto,) async { - final response = await deleteStacksWithHttpInfo(bulkIdsDto,); + Future deleteStacks(BulkIdsDto bulkIdsDto, { Future? abortTrigger, }) async { + final response = await deleteStacksWithHttpInfo(bulkIdsDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -178,7 +181,7 @@ class StacksApi { /// Parameters: /// /// * [String] id (required): - Future getStackWithHttpInfo(String id,) async { + Future getStackWithHttpInfo(String id, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/stacks/{id}' .replaceAll('{id}', id); @@ -201,6 +204,7 @@ class StacksApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -211,8 +215,8 @@ class StacksApi { /// Parameters: /// /// * [String] id (required): - Future getStack(String id,) async { - final response = await getStackWithHttpInfo(id,); + Future getStack(String id, { Future? abortTrigger, }) async { + final response = await getStackWithHttpInfo(id, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -237,7 +241,7 @@ class StacksApi { /// * [String] assetId (required): /// /// * [String] id (required): - Future removeAssetFromStackWithHttpInfo(String assetId, String id,) async { + Future removeAssetFromStackWithHttpInfo(String assetId, String id, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/stacks/{id}/assets/{assetId}' .replaceAll('{assetId}', assetId) @@ -261,6 +265,7 @@ class StacksApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -273,8 +278,8 @@ class StacksApi { /// * [String] assetId (required): /// /// * [String] id (required): - Future removeAssetFromStack(String assetId, String id,) async { - final response = await removeAssetFromStackWithHttpInfo(assetId, id,); + Future removeAssetFromStack(String assetId, String id, { Future? abortTrigger, }) async { + final response = await removeAssetFromStackWithHttpInfo(assetId, id, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -290,7 +295,7 @@ class StacksApi { /// /// * [String] primaryAssetId: /// Filter by primary asset ID - Future searchStacksWithHttpInfo({ String? primaryAssetId, }) async { + Future searchStacksWithHttpInfo({ String? primaryAssetId, Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/stacks'; @@ -316,6 +321,7 @@ class StacksApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -327,8 +333,8 @@ class StacksApi { /// /// * [String] primaryAssetId: /// Filter by primary asset ID - Future?> searchStacks({ String? primaryAssetId, }) async { - final response = await searchStacksWithHttpInfo( primaryAssetId: primaryAssetId, ); + Future?> searchStacks({ String? primaryAssetId, Future? abortTrigger, }) async { + final response = await searchStacksWithHttpInfo(primaryAssetId: primaryAssetId, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -356,7 +362,7 @@ class StacksApi { /// * [String] id (required): /// /// * [StackUpdateDto] stackUpdateDto (required): - Future updateStackWithHttpInfo(String id, StackUpdateDto stackUpdateDto,) async { + Future updateStackWithHttpInfo(String id, StackUpdateDto stackUpdateDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/stacks/{id}' .replaceAll('{id}', id); @@ -379,6 +385,7 @@ class StacksApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -391,8 +398,8 @@ class StacksApi { /// * [String] id (required): /// /// * [StackUpdateDto] stackUpdateDto (required): - Future updateStack(String id, StackUpdateDto stackUpdateDto,) async { - final response = await updateStackWithHttpInfo(id, stackUpdateDto,); + Future updateStack(String id, StackUpdateDto stackUpdateDto, { Future? abortTrigger, }) async { + final response = await updateStackWithHttpInfo(id, stackUpdateDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } diff --git a/mobile/openapi/lib/api/sync_api.dart b/mobile/openapi/lib/api/sync_api.dart index e7bc822ace..c2a57c3395 100644 --- a/mobile/openapi/lib/api/sync_api.dart +++ b/mobile/openapi/lib/api/sync_api.dart @@ -25,7 +25,7 @@ class SyncApi { /// Parameters: /// /// * [SyncAckDeleteDto] syncAckDeleteDto (required): - Future deleteSyncAckWithHttpInfo(SyncAckDeleteDto syncAckDeleteDto,) async { + Future deleteSyncAckWithHttpInfo(SyncAckDeleteDto syncAckDeleteDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/sync/ack'; @@ -47,6 +47,7 @@ class SyncApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -57,8 +58,8 @@ class SyncApi { /// Parameters: /// /// * [SyncAckDeleteDto] syncAckDeleteDto (required): - Future deleteSyncAck(SyncAckDeleteDto syncAckDeleteDto,) async { - final response = await deleteSyncAckWithHttpInfo(syncAckDeleteDto,); + Future deleteSyncAck(SyncAckDeleteDto syncAckDeleteDto, { Future? abortTrigger, }) async { + final response = await deleteSyncAckWithHttpInfo(syncAckDeleteDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -69,7 +70,7 @@ class SyncApi { /// Retrieve the synchronization acknowledgments for the current session. /// /// Note: This method returns the HTTP [Response]. - Future getSyncAckWithHttpInfo() async { + Future getSyncAckWithHttpInfo({ Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/sync/ack'; @@ -91,14 +92,15 @@ class SyncApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } /// Retrieve acknowledgements /// /// Retrieve the synchronization acknowledgments for the current session. - Future?> getSyncAck() async { - final response = await getSyncAckWithHttpInfo(); + Future?> getSyncAck({ Future? abortTrigger, }) async { + final response = await getSyncAckWithHttpInfo(abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -124,7 +126,7 @@ class SyncApi { /// Parameters: /// /// * [SyncStreamDto] syncStreamDto (required): - Future getSyncStreamWithHttpInfo(SyncStreamDto syncStreamDto,) async { + Future getSyncStreamWithHttpInfo(SyncStreamDto syncStreamDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/sync/stream'; @@ -146,6 +148,7 @@ class SyncApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -156,8 +159,8 @@ class SyncApi { /// Parameters: /// /// * [SyncStreamDto] syncStreamDto (required): - Future getSyncStream(SyncStreamDto syncStreamDto,) async { - final response = await getSyncStreamWithHttpInfo(syncStreamDto,); + Future getSyncStream(SyncStreamDto syncStreamDto, { Future? abortTrigger, }) async { + final response = await getSyncStreamWithHttpInfo(syncStreamDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -172,7 +175,7 @@ class SyncApi { /// Parameters: /// /// * [SyncAckSetDto] syncAckSetDto (required): - Future sendSyncAckWithHttpInfo(SyncAckSetDto syncAckSetDto,) async { + Future sendSyncAckWithHttpInfo(SyncAckSetDto syncAckSetDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/sync/ack'; @@ -194,6 +197,7 @@ class SyncApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -204,8 +208,8 @@ class SyncApi { /// Parameters: /// /// * [SyncAckSetDto] syncAckSetDto (required): - Future sendSyncAck(SyncAckSetDto syncAckSetDto,) async { - final response = await sendSyncAckWithHttpInfo(syncAckSetDto,); + Future sendSyncAck(SyncAckSetDto syncAckSetDto, { Future? abortTrigger, }) async { + final response = await sendSyncAckWithHttpInfo(syncAckSetDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } diff --git a/mobile/openapi/lib/api/system_config_api.dart b/mobile/openapi/lib/api/system_config_api.dart index b04da71273..ba5b82263a 100644 --- a/mobile/openapi/lib/api/system_config_api.dart +++ b/mobile/openapi/lib/api/system_config_api.dart @@ -21,7 +21,7 @@ class SystemConfigApi { /// Retrieve the current system configuration. /// /// Note: This method returns the HTTP [Response]. - Future getConfigWithHttpInfo() async { + Future getConfigWithHttpInfo({ Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/system-config'; @@ -43,14 +43,15 @@ class SystemConfigApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } /// Get system configuration /// /// Retrieve the current system configuration. - Future getConfig() async { - final response = await getConfigWithHttpInfo(); + Future getConfig({ Future? abortTrigger, }) async { + final response = await getConfigWithHttpInfo(abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -69,7 +70,7 @@ class SystemConfigApi { /// Retrieve the default values for the system configuration. /// /// Note: This method returns the HTTP [Response]. - Future getConfigDefaultsWithHttpInfo() async { + Future getConfigDefaultsWithHttpInfo({ Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/system-config/defaults'; @@ -91,14 +92,15 @@ class SystemConfigApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } /// Get system configuration defaults /// /// Retrieve the default values for the system configuration. - Future getConfigDefaults() async { - final response = await getConfigDefaultsWithHttpInfo(); + Future getConfigDefaults({ Future? abortTrigger, }) async { + final response = await getConfigDefaultsWithHttpInfo(abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -117,7 +119,7 @@ class SystemConfigApi { /// Retrieve exemplary storage template options. /// /// Note: This method returns the HTTP [Response]. - Future getStorageTemplateOptionsWithHttpInfo() async { + Future getStorageTemplateOptionsWithHttpInfo({ Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/system-config/storage-template-options'; @@ -139,14 +141,15 @@ class SystemConfigApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } /// Get storage template options /// /// Retrieve exemplary storage template options. - Future getStorageTemplateOptions() async { - final response = await getStorageTemplateOptionsWithHttpInfo(); + Future getStorageTemplateOptions({ Future? abortTrigger, }) async { + final response = await getStorageTemplateOptionsWithHttpInfo(abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -169,7 +172,7 @@ class SystemConfigApi { /// Parameters: /// /// * [SystemConfigDto] systemConfigDto (required): - Future updateConfigWithHttpInfo(SystemConfigDto systemConfigDto,) async { + Future updateConfigWithHttpInfo(SystemConfigDto systemConfigDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/system-config'; @@ -191,6 +194,7 @@ class SystemConfigApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -201,8 +205,8 @@ class SystemConfigApi { /// Parameters: /// /// * [SystemConfigDto] systemConfigDto (required): - Future updateConfig(SystemConfigDto systemConfigDto,) async { - final response = await updateConfigWithHttpInfo(systemConfigDto,); + Future updateConfig(SystemConfigDto systemConfigDto, { Future? abortTrigger, }) async { + final response = await updateConfigWithHttpInfo(systemConfigDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } diff --git a/mobile/openapi/lib/api/system_metadata_api.dart b/mobile/openapi/lib/api/system_metadata_api.dart index 63fd7628ec..a1429b54b0 100644 --- a/mobile/openapi/lib/api/system_metadata_api.dart +++ b/mobile/openapi/lib/api/system_metadata_api.dart @@ -21,7 +21,7 @@ class SystemMetadataApi { /// Retrieve the current admin onboarding status. /// /// Note: This method returns the HTTP [Response]. - Future getAdminOnboardingWithHttpInfo() async { + Future getAdminOnboardingWithHttpInfo({ Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/system-metadata/admin-onboarding'; @@ -43,14 +43,15 @@ class SystemMetadataApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } /// Retrieve admin onboarding /// /// Retrieve the current admin onboarding status. - Future getAdminOnboarding() async { - final response = await getAdminOnboardingWithHttpInfo(); + Future getAdminOnboarding({ Future? abortTrigger, }) async { + final response = await getAdminOnboardingWithHttpInfo(abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -69,7 +70,7 @@ class SystemMetadataApi { /// Retrieve the current state of the reverse geocoding import. /// /// Note: This method returns the HTTP [Response]. - Future getReverseGeocodingStateWithHttpInfo() async { + Future getReverseGeocodingStateWithHttpInfo({ Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/system-metadata/reverse-geocoding-state'; @@ -91,14 +92,15 @@ class SystemMetadataApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } /// Retrieve reverse geocoding state /// /// Retrieve the current state of the reverse geocoding import. - Future getReverseGeocodingState() async { - final response = await getReverseGeocodingStateWithHttpInfo(); + Future getReverseGeocodingState({ Future? abortTrigger, }) async { + final response = await getReverseGeocodingStateWithHttpInfo(abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -117,7 +119,7 @@ class SystemMetadataApi { /// Retrieve the current state of the version check process. /// /// Note: This method returns the HTTP [Response]. - Future getVersionCheckStateWithHttpInfo() async { + Future getVersionCheckStateWithHttpInfo({ Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/system-metadata/version-check-state'; @@ -139,14 +141,15 @@ class SystemMetadataApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } /// Retrieve version check state /// /// Retrieve the current state of the version check process. - Future getVersionCheckState() async { - final response = await getVersionCheckStateWithHttpInfo(); + Future getVersionCheckState({ Future? abortTrigger, }) async { + final response = await getVersionCheckStateWithHttpInfo(abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -169,7 +172,7 @@ class SystemMetadataApi { /// Parameters: /// /// * [AdminOnboardingUpdateDto] adminOnboardingUpdateDto (required): - Future updateAdminOnboardingWithHttpInfo(AdminOnboardingUpdateDto adminOnboardingUpdateDto,) async { + Future updateAdminOnboardingWithHttpInfo(AdminOnboardingUpdateDto adminOnboardingUpdateDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/system-metadata/admin-onboarding'; @@ -191,6 +194,7 @@ class SystemMetadataApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -201,8 +205,8 @@ class SystemMetadataApi { /// Parameters: /// /// * [AdminOnboardingUpdateDto] adminOnboardingUpdateDto (required): - Future updateAdminOnboarding(AdminOnboardingUpdateDto adminOnboardingUpdateDto,) async { - final response = await updateAdminOnboardingWithHttpInfo(adminOnboardingUpdateDto,); + Future updateAdminOnboarding(AdminOnboardingUpdateDto adminOnboardingUpdateDto, { Future? abortTrigger, }) async { + final response = await updateAdminOnboardingWithHttpInfo(adminOnboardingUpdateDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } diff --git a/mobile/openapi/lib/api/tags_api.dart b/mobile/openapi/lib/api/tags_api.dart index a6840f9483..c3cf9f545c 100644 --- a/mobile/openapi/lib/api/tags_api.dart +++ b/mobile/openapi/lib/api/tags_api.dart @@ -25,7 +25,7 @@ class TagsApi { /// Parameters: /// /// * [TagBulkAssetsDto] tagBulkAssetsDto (required): - Future bulkTagAssetsWithHttpInfo(TagBulkAssetsDto tagBulkAssetsDto,) async { + Future bulkTagAssetsWithHttpInfo(TagBulkAssetsDto tagBulkAssetsDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/tags/assets'; @@ -47,6 +47,7 @@ class TagsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -57,8 +58,8 @@ class TagsApi { /// Parameters: /// /// * [TagBulkAssetsDto] tagBulkAssetsDto (required): - Future bulkTagAssets(TagBulkAssetsDto tagBulkAssetsDto,) async { - final response = await bulkTagAssetsWithHttpInfo(tagBulkAssetsDto,); + Future bulkTagAssets(TagBulkAssetsDto tagBulkAssetsDto, { Future? abortTrigger, }) async { + final response = await bulkTagAssetsWithHttpInfo(tagBulkAssetsDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -81,7 +82,7 @@ class TagsApi { /// Parameters: /// /// * [TagCreateDto] tagCreateDto (required): - Future createTagWithHttpInfo(TagCreateDto tagCreateDto,) async { + Future createTagWithHttpInfo(TagCreateDto tagCreateDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/tags'; @@ -103,6 +104,7 @@ class TagsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -113,8 +115,8 @@ class TagsApi { /// Parameters: /// /// * [TagCreateDto] tagCreateDto (required): - Future createTag(TagCreateDto tagCreateDto,) async { - final response = await createTagWithHttpInfo(tagCreateDto,); + Future createTag(TagCreateDto tagCreateDto, { Future? abortTrigger, }) async { + final response = await createTagWithHttpInfo(tagCreateDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -137,7 +139,7 @@ class TagsApi { /// Parameters: /// /// * [String] id (required): - Future deleteTagWithHttpInfo(String id,) async { + Future deleteTagWithHttpInfo(String id, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/tags/{id}' .replaceAll('{id}', id); @@ -160,6 +162,7 @@ class TagsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -170,8 +173,8 @@ class TagsApi { /// Parameters: /// /// * [String] id (required): - Future deleteTag(String id,) async { - final response = await deleteTagWithHttpInfo(id,); + Future deleteTag(String id, { Future? abortTrigger, }) async { + final response = await deleteTagWithHttpInfo(id, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -182,7 +185,7 @@ class TagsApi { /// Retrieve a list of all tags. /// /// Note: This method returns the HTTP [Response]. - Future getAllTagsWithHttpInfo() async { + Future getAllTagsWithHttpInfo({ Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/tags'; @@ -204,14 +207,15 @@ class TagsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } /// Retrieve tags /// /// Retrieve a list of all tags. - Future?> getAllTags() async { - final response = await getAllTagsWithHttpInfo(); + Future?> getAllTags({ Future? abortTrigger, }) async { + final response = await getAllTagsWithHttpInfo(abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -237,7 +241,7 @@ class TagsApi { /// Parameters: /// /// * [String] id (required): - Future getTagByIdWithHttpInfo(String id,) async { + Future getTagByIdWithHttpInfo(String id, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/tags/{id}' .replaceAll('{id}', id); @@ -260,6 +264,7 @@ class TagsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -270,8 +275,8 @@ class TagsApi { /// Parameters: /// /// * [String] id (required): - Future getTagById(String id,) async { - final response = await getTagByIdWithHttpInfo(id,); + Future getTagById(String id, { Future? abortTrigger, }) async { + final response = await getTagByIdWithHttpInfo(id, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -296,7 +301,7 @@ class TagsApi { /// * [String] id (required): /// /// * [BulkIdsDto] bulkIdsDto (required): - Future tagAssetsWithHttpInfo(String id, BulkIdsDto bulkIdsDto,) async { + Future tagAssetsWithHttpInfo(String id, BulkIdsDto bulkIdsDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/tags/{id}/assets' .replaceAll('{id}', id); @@ -319,6 +324,7 @@ class TagsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -331,8 +337,8 @@ class TagsApi { /// * [String] id (required): /// /// * [BulkIdsDto] bulkIdsDto (required): - Future?> tagAssets(String id, BulkIdsDto bulkIdsDto,) async { - final response = await tagAssetsWithHttpInfo(id, bulkIdsDto,); + Future?> tagAssets(String id, BulkIdsDto bulkIdsDto, { Future? abortTrigger, }) async { + final response = await tagAssetsWithHttpInfo(id, bulkIdsDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -360,7 +366,7 @@ class TagsApi { /// * [String] id (required): /// /// * [BulkIdsDto] bulkIdsDto (required): - Future untagAssetsWithHttpInfo(String id, BulkIdsDto bulkIdsDto,) async { + Future untagAssetsWithHttpInfo(String id, BulkIdsDto bulkIdsDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/tags/{id}/assets' .replaceAll('{id}', id); @@ -383,6 +389,7 @@ class TagsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -395,8 +402,8 @@ class TagsApi { /// * [String] id (required): /// /// * [BulkIdsDto] bulkIdsDto (required): - Future?> untagAssets(String id, BulkIdsDto bulkIdsDto,) async { - final response = await untagAssetsWithHttpInfo(id, bulkIdsDto,); + Future?> untagAssets(String id, BulkIdsDto bulkIdsDto, { Future? abortTrigger, }) async { + final response = await untagAssetsWithHttpInfo(id, bulkIdsDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -424,7 +431,7 @@ class TagsApi { /// * [String] id (required): /// /// * [TagUpdateDto] tagUpdateDto (required): - Future updateTagWithHttpInfo(String id, TagUpdateDto tagUpdateDto,) async { + Future updateTagWithHttpInfo(String id, TagUpdateDto tagUpdateDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/tags/{id}' .replaceAll('{id}', id); @@ -447,6 +454,7 @@ class TagsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -459,8 +467,8 @@ class TagsApi { /// * [String] id (required): /// /// * [TagUpdateDto] tagUpdateDto (required): - Future updateTag(String id, TagUpdateDto tagUpdateDto,) async { - final response = await updateTagWithHttpInfo(id, tagUpdateDto,); + Future updateTag(String id, TagUpdateDto tagUpdateDto, { Future? abortTrigger, }) async { + final response = await updateTagWithHttpInfo(id, tagUpdateDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -483,7 +491,7 @@ class TagsApi { /// Parameters: /// /// * [TagUpsertDto] tagUpsertDto (required): - Future upsertTagsWithHttpInfo(TagUpsertDto tagUpsertDto,) async { + Future upsertTagsWithHttpInfo(TagUpsertDto tagUpsertDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/tags'; @@ -505,6 +513,7 @@ class TagsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -515,8 +524,8 @@ class TagsApi { /// Parameters: /// /// * [TagUpsertDto] tagUpsertDto (required): - Future?> upsertTags(TagUpsertDto tagUpsertDto,) async { - final response = await upsertTagsWithHttpInfo(tagUpsertDto,); + Future?> upsertTags(TagUpsertDto tagUpsertDto, { Future? abortTrigger, }) async { + final response = await upsertTagsWithHttpInfo(tagUpsertDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } diff --git a/mobile/openapi/lib/api/timeline_api.dart b/mobile/openapi/lib/api/timeline_api.dart index 30a4c123f1..a85aee2d7a 100644 --- a/mobile/openapi/lib/api/timeline_api.dart +++ b/mobile/openapi/lib/api/timeline_api.dart @@ -44,6 +44,9 @@ class TimelineApi { /// * [AssetOrder] order: /// Sort order for assets within time buckets (ASC for oldest first, DESC for newest first) /// + /// * [AssetOrderBy] orderBy: + /// Date to group and order assets by (takenAt for date taken, createdAt for date added to Immich) + /// /// * [String] personId: /// Filter assets containing a specific person (face recognition) /// @@ -66,7 +69,7 @@ class TimelineApi { /// /// * [bool] withStacked: /// Include stacked assets in the response. When true, only primary assets from stacks are returned. - Future getTimeBucketWithHttpInfo(String timeBucket, { String? albumId, String? bbox, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? slug, String? tagId, String? userId, AssetVisibility? visibility, bool? withCoordinates, bool? withPartners, bool? withStacked, }) async { + Future getTimeBucketWithHttpInfo(String timeBucket, { String? albumId, String? bbox, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, AssetOrderBy? orderBy, String? personId, String? slug, String? tagId, String? userId, AssetVisibility? visibility, bool? withCoordinates, bool? withPartners, bool? withStacked, Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/timeline/bucket'; @@ -95,6 +98,9 @@ class TimelineApi { if (order != null) { queryParams.addAll(_queryParams('', 'order', order)); } + if (orderBy != null) { + queryParams.addAll(_queryParams('', 'orderBy', orderBy)); + } if (personId != null) { queryParams.addAll(_queryParams('', 'personId', personId)); } @@ -132,6 +138,7 @@ class TimelineApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -161,6 +168,9 @@ class TimelineApi { /// * [AssetOrder] order: /// Sort order for assets within time buckets (ASC for oldest first, DESC for newest first) /// + /// * [AssetOrderBy] orderBy: + /// Date to group and order assets by (takenAt for date taken, createdAt for date added to Immich) + /// /// * [String] personId: /// Filter assets containing a specific person (face recognition) /// @@ -183,8 +193,8 @@ class TimelineApi { /// /// * [bool] withStacked: /// Include stacked assets in the response. When true, only primary assets from stacks are returned. - Future getTimeBucket(String timeBucket, { String? albumId, String? bbox, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? slug, String? tagId, String? userId, AssetVisibility? visibility, bool? withCoordinates, bool? withPartners, bool? withStacked, }) async { - final response = await getTimeBucketWithHttpInfo(timeBucket, albumId: albumId, bbox: bbox, isFavorite: isFavorite, isTrashed: isTrashed, key: key, order: order, personId: personId, slug: slug, tagId: tagId, userId: userId, visibility: visibility, withCoordinates: withCoordinates, withPartners: withPartners, withStacked: withStacked, ); + Future getTimeBucket(String timeBucket, { String? albumId, String? bbox, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, AssetOrderBy? orderBy, String? personId, String? slug, String? tagId, String? userId, AssetVisibility? visibility, bool? withCoordinates, bool? withPartners, bool? withStacked, Future? abortTrigger, }) async { + final response = await getTimeBucketWithHttpInfo(timeBucket, albumId: albumId, bbox: bbox, isFavorite: isFavorite, isTrashed: isTrashed, key: key, order: order, orderBy: orderBy, personId: personId, slug: slug, tagId: tagId, userId: userId, visibility: visibility, withCoordinates: withCoordinates, withPartners: withPartners, withStacked: withStacked, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -223,6 +233,9 @@ class TimelineApi { /// * [AssetOrder] order: /// Sort order for assets within time buckets (ASC for oldest first, DESC for newest first) /// + /// * [AssetOrderBy] orderBy: + /// Date to group and order assets by (takenAt for date taken, createdAt for date added to Immich) + /// /// * [String] personId: /// Filter assets containing a specific person (face recognition) /// @@ -245,7 +258,7 @@ class TimelineApi { /// /// * [bool] withStacked: /// Include stacked assets in the response. When true, only primary assets from stacks are returned. - Future getTimeBucketsWithHttpInfo({ String? albumId, String? bbox, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? slug, String? tagId, String? userId, AssetVisibility? visibility, bool? withCoordinates, bool? withPartners, bool? withStacked, }) async { + Future getTimeBucketsWithHttpInfo({ String? albumId, String? bbox, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, AssetOrderBy? orderBy, String? personId, String? slug, String? tagId, String? userId, AssetVisibility? visibility, bool? withCoordinates, bool? withPartners, bool? withStacked, Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/timeline/buckets'; @@ -274,6 +287,9 @@ class TimelineApi { if (order != null) { queryParams.addAll(_queryParams('', 'order', order)); } + if (orderBy != null) { + queryParams.addAll(_queryParams('', 'orderBy', orderBy)); + } if (personId != null) { queryParams.addAll(_queryParams('', 'personId', personId)); } @@ -310,6 +326,7 @@ class TimelineApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -336,6 +353,9 @@ class TimelineApi { /// * [AssetOrder] order: /// Sort order for assets within time buckets (ASC for oldest first, DESC for newest first) /// + /// * [AssetOrderBy] orderBy: + /// Date to group and order assets by (takenAt for date taken, createdAt for date added to Immich) + /// /// * [String] personId: /// Filter assets containing a specific person (face recognition) /// @@ -358,8 +378,8 @@ class TimelineApi { /// /// * [bool] withStacked: /// Include stacked assets in the response. When true, only primary assets from stacks are returned. - Future?> getTimeBuckets({ String? albumId, String? bbox, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? slug, String? tagId, String? userId, AssetVisibility? visibility, bool? withCoordinates, bool? withPartners, bool? withStacked, }) async { - final response = await getTimeBucketsWithHttpInfo( albumId: albumId, bbox: bbox, isFavorite: isFavorite, isTrashed: isTrashed, key: key, order: order, personId: personId, slug: slug, tagId: tagId, userId: userId, visibility: visibility, withCoordinates: withCoordinates, withPartners: withPartners, withStacked: withStacked, ); + Future?> getTimeBuckets({ String? albumId, String? bbox, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, AssetOrderBy? orderBy, String? personId, String? slug, String? tagId, String? userId, AssetVisibility? visibility, bool? withCoordinates, bool? withPartners, bool? withStacked, Future? abortTrigger, }) async { + final response = await getTimeBucketsWithHttpInfo(albumId: albumId, bbox: bbox, isFavorite: isFavorite, isTrashed: isTrashed, key: key, order: order, orderBy: orderBy, personId: personId, slug: slug, tagId: tagId, userId: userId, visibility: visibility, withCoordinates: withCoordinates, withPartners: withPartners, withStacked: withStacked, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } diff --git a/mobile/openapi/lib/api/trash_api.dart b/mobile/openapi/lib/api/trash_api.dart index f1dcbb8896..7b593e5111 100644 --- a/mobile/openapi/lib/api/trash_api.dart +++ b/mobile/openapi/lib/api/trash_api.dart @@ -21,7 +21,7 @@ class TrashApi { /// Permanently delete all items in the trash. /// /// Note: This method returns the HTTP [Response]. - Future emptyTrashWithHttpInfo() async { + Future emptyTrashWithHttpInfo({ Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/trash/empty'; @@ -43,14 +43,15 @@ class TrashApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } /// Empty trash /// /// Permanently delete all items in the trash. - Future emptyTrash() async { - final response = await emptyTrashWithHttpInfo(); + Future emptyTrash({ Future? abortTrigger, }) async { + final response = await emptyTrashWithHttpInfo(abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -73,7 +74,7 @@ class TrashApi { /// Parameters: /// /// * [BulkIdsDto] bulkIdsDto (required): - Future restoreAssetsWithHttpInfo(BulkIdsDto bulkIdsDto,) async { + Future restoreAssetsWithHttpInfo(BulkIdsDto bulkIdsDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/trash/restore/assets'; @@ -95,6 +96,7 @@ class TrashApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -105,8 +107,8 @@ class TrashApi { /// Parameters: /// /// * [BulkIdsDto] bulkIdsDto (required): - Future restoreAssets(BulkIdsDto bulkIdsDto,) async { - final response = await restoreAssetsWithHttpInfo(bulkIdsDto,); + Future restoreAssets(BulkIdsDto bulkIdsDto, { Future? abortTrigger, }) async { + final response = await restoreAssetsWithHttpInfo(bulkIdsDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -125,7 +127,7 @@ class TrashApi { /// Restore all items in the trash. /// /// Note: This method returns the HTTP [Response]. - Future restoreTrashWithHttpInfo() async { + Future restoreTrashWithHttpInfo({ Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/trash/restore'; @@ -147,14 +149,15 @@ class TrashApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } /// Restore trash /// /// Restore all items in the trash. - Future restoreTrash() async { - final response = await restoreTrashWithHttpInfo(); + Future restoreTrash({ Future? abortTrigger, }) async { + final response = await restoreTrashWithHttpInfo(abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } diff --git a/mobile/openapi/lib/api/users_admin_api.dart b/mobile/openapi/lib/api/users_admin_api.dart index 5e165ffd5d..fd6b43d9ce 100644 --- a/mobile/openapi/lib/api/users_admin_api.dart +++ b/mobile/openapi/lib/api/users_admin_api.dart @@ -25,7 +25,7 @@ class UsersAdminApi { /// Parameters: /// /// * [UserAdminCreateDto] userAdminCreateDto (required): - Future createUserAdminWithHttpInfo(UserAdminCreateDto userAdminCreateDto,) async { + Future createUserAdminWithHttpInfo(UserAdminCreateDto userAdminCreateDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/admin/users'; @@ -47,6 +47,7 @@ class UsersAdminApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -57,8 +58,8 @@ class UsersAdminApi { /// Parameters: /// /// * [UserAdminCreateDto] userAdminCreateDto (required): - Future createUserAdmin(UserAdminCreateDto userAdminCreateDto,) async { - final response = await createUserAdminWithHttpInfo(userAdminCreateDto,); + Future createUserAdmin(UserAdminCreateDto userAdminCreateDto, { Future? abortTrigger, }) async { + final response = await createUserAdminWithHttpInfo(userAdminCreateDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -83,7 +84,7 @@ class UsersAdminApi { /// * [String] id (required): /// /// * [UserAdminDeleteDto] userAdminDeleteDto (required): - Future deleteUserAdminWithHttpInfo(String id, UserAdminDeleteDto userAdminDeleteDto,) async { + Future deleteUserAdminWithHttpInfo(String id, UserAdminDeleteDto userAdminDeleteDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/admin/users/{id}' .replaceAll('{id}', id); @@ -106,6 +107,7 @@ class UsersAdminApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -118,8 +120,8 @@ class UsersAdminApi { /// * [String] id (required): /// /// * [UserAdminDeleteDto] userAdminDeleteDto (required): - Future deleteUserAdmin(String id, UserAdminDeleteDto userAdminDeleteDto,) async { - final response = await deleteUserAdminWithHttpInfo(id, userAdminDeleteDto,); + Future deleteUserAdmin(String id, UserAdminDeleteDto userAdminDeleteDto, { Future? abortTrigger, }) async { + final response = await deleteUserAdminWithHttpInfo(id, userAdminDeleteDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -142,7 +144,7 @@ class UsersAdminApi { /// Parameters: /// /// * [String] id (required): - Future getUserAdminWithHttpInfo(String id,) async { + Future getUserAdminWithHttpInfo(String id, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/admin/users/{id}' .replaceAll('{id}', id); @@ -165,6 +167,7 @@ class UsersAdminApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -175,8 +178,8 @@ class UsersAdminApi { /// Parameters: /// /// * [String] id (required): - Future getUserAdmin(String id,) async { - final response = await getUserAdminWithHttpInfo(id,); + Future getUserAdmin(String id, { Future? abortTrigger, }) async { + final response = await getUserAdminWithHttpInfo(id, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -199,7 +202,7 @@ class UsersAdminApi { /// Parameters: /// /// * [String] id (required): - Future getUserPreferencesAdminWithHttpInfo(String id,) async { + Future getUserPreferencesAdminWithHttpInfo(String id, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/admin/users/{id}/preferences' .replaceAll('{id}', id); @@ -222,6 +225,7 @@ class UsersAdminApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -232,8 +236,8 @@ class UsersAdminApi { /// Parameters: /// /// * [String] id (required): - Future getUserPreferencesAdmin(String id,) async { - final response = await getUserPreferencesAdminWithHttpInfo(id,); + Future getUserPreferencesAdmin(String id, { Future? abortTrigger, }) async { + final response = await getUserPreferencesAdminWithHttpInfo(id, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -256,7 +260,7 @@ class UsersAdminApi { /// Parameters: /// /// * [String] id (required): - Future getUserSessionsAdminWithHttpInfo(String id,) async { + Future getUserSessionsAdminWithHttpInfo(String id, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/admin/users/{id}/sessions' .replaceAll('{id}', id); @@ -279,6 +283,7 @@ class UsersAdminApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -289,8 +294,8 @@ class UsersAdminApi { /// Parameters: /// /// * [String] id (required): - Future?> getUserSessionsAdmin(String id,) async { - final response = await getUserSessionsAdminWithHttpInfo(id,); + Future?> getUserSessionsAdmin(String id, { Future? abortTrigger, }) async { + final response = await getUserSessionsAdminWithHttpInfo(id, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -324,7 +329,7 @@ class UsersAdminApi { /// Filter by trash status /// /// * [AssetVisibility] visibility: - Future getUserStatisticsAdminWithHttpInfo(String id, { bool? isFavorite, bool? isTrashed, AssetVisibility? visibility, }) async { + Future getUserStatisticsAdminWithHttpInfo(String id, { bool? isFavorite, bool? isTrashed, AssetVisibility? visibility, Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/admin/users/{id}/statistics' .replaceAll('{id}', id); @@ -357,6 +362,7 @@ class UsersAdminApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -375,8 +381,8 @@ class UsersAdminApi { /// Filter by trash status /// /// * [AssetVisibility] visibility: - Future getUserStatisticsAdmin(String id, { bool? isFavorite, bool? isTrashed, AssetVisibility? visibility, }) async { - final response = await getUserStatisticsAdminWithHttpInfo(id, isFavorite: isFavorite, isTrashed: isTrashed, visibility: visibility, ); + Future getUserStatisticsAdmin(String id, { bool? isFavorite, bool? isTrashed, AssetVisibility? visibility, Future? abortTrigger, }) async { + final response = await getUserStatisticsAdminWithHttpInfo(id, isFavorite: isFavorite, isTrashed: isTrashed, visibility: visibility, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -399,7 +405,7 @@ class UsersAdminApi { /// Parameters: /// /// * [String] id (required): - Future restoreUserAdminWithHttpInfo(String id,) async { + Future restoreUserAdminWithHttpInfo(String id, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/admin/users/{id}/restore' .replaceAll('{id}', id); @@ -422,6 +428,7 @@ class UsersAdminApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -432,8 +439,8 @@ class UsersAdminApi { /// Parameters: /// /// * [String] id (required): - Future restoreUserAdmin(String id,) async { - final response = await restoreUserAdminWithHttpInfo(id,); + Future restoreUserAdmin(String id, { Future? abortTrigger, }) async { + final response = await restoreUserAdminWithHttpInfo(id, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -460,7 +467,7 @@ class UsersAdminApi { /// /// * [bool] withDeleted: /// Include deleted users - Future searchUsersAdminWithHttpInfo({ String? id, bool? withDeleted, }) async { + Future searchUsersAdminWithHttpInfo({ String? id, bool? withDeleted, Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/admin/users'; @@ -489,6 +496,7 @@ class UsersAdminApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -503,8 +511,8 @@ class UsersAdminApi { /// /// * [bool] withDeleted: /// Include deleted users - Future?> searchUsersAdmin({ String? id, bool? withDeleted, }) async { - final response = await searchUsersAdminWithHttpInfo( id: id, withDeleted: withDeleted, ); + Future?> searchUsersAdmin({ String? id, bool? withDeleted, Future? abortTrigger, }) async { + final response = await searchUsersAdminWithHttpInfo(id: id, withDeleted: withDeleted, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -532,7 +540,7 @@ class UsersAdminApi { /// * [String] id (required): /// /// * [UserAdminUpdateDto] userAdminUpdateDto (required): - Future updateUserAdminWithHttpInfo(String id, UserAdminUpdateDto userAdminUpdateDto,) async { + Future updateUserAdminWithHttpInfo(String id, UserAdminUpdateDto userAdminUpdateDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/admin/users/{id}' .replaceAll('{id}', id); @@ -555,6 +563,7 @@ class UsersAdminApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -567,8 +576,8 @@ class UsersAdminApi { /// * [String] id (required): /// /// * [UserAdminUpdateDto] userAdminUpdateDto (required): - Future updateUserAdmin(String id, UserAdminUpdateDto userAdminUpdateDto,) async { - final response = await updateUserAdminWithHttpInfo(id, userAdminUpdateDto,); + Future updateUserAdmin(String id, UserAdminUpdateDto userAdminUpdateDto, { Future? abortTrigger, }) async { + final response = await updateUserAdminWithHttpInfo(id, userAdminUpdateDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -593,7 +602,7 @@ class UsersAdminApi { /// * [String] id (required): /// /// * [UserPreferencesUpdateDto] userPreferencesUpdateDto (required): - Future updateUserPreferencesAdminWithHttpInfo(String id, UserPreferencesUpdateDto userPreferencesUpdateDto,) async { + Future updateUserPreferencesAdminWithHttpInfo(String id, UserPreferencesUpdateDto userPreferencesUpdateDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/admin/users/{id}/preferences' .replaceAll('{id}', id); @@ -616,6 +625,7 @@ class UsersAdminApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -628,8 +638,8 @@ class UsersAdminApi { /// * [String] id (required): /// /// * [UserPreferencesUpdateDto] userPreferencesUpdateDto (required): - Future updateUserPreferencesAdmin(String id, UserPreferencesUpdateDto userPreferencesUpdateDto,) async { - final response = await updateUserPreferencesAdminWithHttpInfo(id, userPreferencesUpdateDto,); + Future updateUserPreferencesAdmin(String id, UserPreferencesUpdateDto userPreferencesUpdateDto, { Future? abortTrigger, }) async { + final response = await updateUserPreferencesAdminWithHttpInfo(id, userPreferencesUpdateDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } diff --git a/mobile/openapi/lib/api/users_api.dart b/mobile/openapi/lib/api/users_api.dart index 401cf4e94b..a7fac3ea66 100644 --- a/mobile/openapi/lib/api/users_api.dart +++ b/mobile/openapi/lib/api/users_api.dart @@ -26,7 +26,7 @@ class UsersApi { /// /// * [MultipartFile] file (required): /// Profile image file - Future createProfileImageWithHttpInfo(MultipartFile file,) async { + Future createProfileImageWithHttpInfo(MultipartFile file, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/users/profile-image'; @@ -58,6 +58,7 @@ class UsersApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -69,8 +70,8 @@ class UsersApi { /// /// * [MultipartFile] file (required): /// Profile image file - Future createProfileImage(MultipartFile file,) async { - final response = await createProfileImageWithHttpInfo(file,); + Future createProfileImage(MultipartFile file, { Future? abortTrigger, }) async { + final response = await createProfileImageWithHttpInfo(file, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -89,7 +90,7 @@ class UsersApi { /// Delete the profile image of the current user. /// /// Note: This method returns the HTTP [Response]. - Future deleteProfileImageWithHttpInfo() async { + Future deleteProfileImageWithHttpInfo({ Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/users/profile-image'; @@ -111,14 +112,15 @@ class UsersApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } /// Delete user profile image /// /// Delete the profile image of the current user. - Future deleteProfileImage() async { - final response = await deleteProfileImageWithHttpInfo(); + Future deleteProfileImage({ Future? abortTrigger, }) async { + final response = await deleteProfileImageWithHttpInfo(abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -129,7 +131,7 @@ class UsersApi { /// Delete the registered product key for the current user. /// /// Note: This method returns the HTTP [Response]. - Future deleteUserLicenseWithHttpInfo() async { + Future deleteUserLicenseWithHttpInfo({ Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/users/me/license'; @@ -151,14 +153,15 @@ class UsersApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } /// Delete user product key /// /// Delete the registered product key for the current user. - Future deleteUserLicense() async { - final response = await deleteUserLicenseWithHttpInfo(); + Future deleteUserLicense({ Future? abortTrigger, }) async { + final response = await deleteUserLicenseWithHttpInfo(abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -169,7 +172,7 @@ class UsersApi { /// Delete the onboarding status of the current user. /// /// Note: This method returns the HTTP [Response]. - Future deleteUserOnboardingWithHttpInfo() async { + Future deleteUserOnboardingWithHttpInfo({ Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/users/me/onboarding'; @@ -191,14 +194,15 @@ class UsersApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } /// Delete user onboarding /// /// Delete the onboarding status of the current user. - Future deleteUserOnboarding() async { - final response = await deleteUserOnboardingWithHttpInfo(); + Future deleteUserOnboarding({ Future? abortTrigger, }) async { + final response = await deleteUserOnboardingWithHttpInfo(abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -209,7 +213,7 @@ class UsersApi { /// Retrieve the preferences for the current user. /// /// Note: This method returns the HTTP [Response]. - Future getMyPreferencesWithHttpInfo() async { + Future getMyPreferencesWithHttpInfo({ Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/users/me/preferences'; @@ -231,14 +235,15 @@ class UsersApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } /// Get my preferences /// /// Retrieve the preferences for the current user. - Future getMyPreferences() async { - final response = await getMyPreferencesWithHttpInfo(); + Future getMyPreferences({ Future? abortTrigger, }) async { + final response = await getMyPreferencesWithHttpInfo(abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -257,7 +262,7 @@ class UsersApi { /// Retrieve information about the user making the API request. /// /// Note: This method returns the HTTP [Response]. - Future getMyUserWithHttpInfo() async { + Future getMyUserWithHttpInfo({ Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/users/me'; @@ -279,14 +284,15 @@ class UsersApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } /// Get current user /// /// Retrieve information about the user making the API request. - Future getMyUser() async { - final response = await getMyUserWithHttpInfo(); + Future getMyUser({ Future? abortTrigger, }) async { + final response = await getMyUserWithHttpInfo(abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -309,7 +315,7 @@ class UsersApi { /// Parameters: /// /// * [String] id (required): - Future getProfileImageWithHttpInfo(String id,) async { + Future getProfileImageWithHttpInfo(String id, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/users/{id}/profile-image' .replaceAll('{id}', id); @@ -332,6 +338,7 @@ class UsersApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -342,8 +349,8 @@ class UsersApi { /// Parameters: /// /// * [String] id (required): - Future getProfileImage(String id,) async { - final response = await getProfileImageWithHttpInfo(id,); + Future getProfileImage(String id, { Future? abortTrigger, }) async { + final response = await getProfileImageWithHttpInfo(id, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -366,7 +373,7 @@ class UsersApi { /// Parameters: /// /// * [String] id (required): - Future getUserWithHttpInfo(String id,) async { + Future getUserWithHttpInfo(String id, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/users/{id}' .replaceAll('{id}', id); @@ -389,6 +396,7 @@ class UsersApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -399,8 +407,8 @@ class UsersApi { /// Parameters: /// /// * [String] id (required): - Future getUser(String id,) async { - final response = await getUserWithHttpInfo(id,); + Future getUser(String id, { Future? abortTrigger, }) async { + final response = await getUserWithHttpInfo(id, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -419,7 +427,7 @@ class UsersApi { /// Retrieve information about whether the current user has a registered product key. /// /// Note: This method returns the HTTP [Response]. - Future getUserLicenseWithHttpInfo() async { + Future getUserLicenseWithHttpInfo({ Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/users/me/license'; @@ -441,14 +449,15 @@ class UsersApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } /// Retrieve user product key /// /// Retrieve information about whether the current user has a registered product key. - Future getUserLicense() async { - final response = await getUserLicenseWithHttpInfo(); + Future getUserLicense({ Future? abortTrigger, }) async { + final response = await getUserLicenseWithHttpInfo(abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -467,7 +476,7 @@ class UsersApi { /// Retrieve the onboarding status of the current user. /// /// Note: This method returns the HTTP [Response]. - Future getUserOnboardingWithHttpInfo() async { + Future getUserOnboardingWithHttpInfo({ Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/users/me/onboarding'; @@ -489,14 +498,15 @@ class UsersApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } /// Retrieve user onboarding /// /// Retrieve the onboarding status of the current user. - Future getUserOnboarding() async { - final response = await getUserOnboardingWithHttpInfo(); + Future getUserOnboarding({ Future? abortTrigger, }) async { + final response = await getUserOnboardingWithHttpInfo(abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -515,7 +525,7 @@ class UsersApi { /// Retrieve a list of all users on the server. /// /// Note: This method returns the HTTP [Response]. - Future searchUsersWithHttpInfo() async { + Future searchUsersWithHttpInfo({ Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/users'; @@ -537,14 +547,15 @@ class UsersApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } /// Get all users /// /// Retrieve a list of all users on the server. - Future?> searchUsers() async { - final response = await searchUsersWithHttpInfo(); + Future?> searchUsers({ Future? abortTrigger, }) async { + final response = await searchUsersWithHttpInfo(abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -570,7 +581,7 @@ class UsersApi { /// Parameters: /// /// * [LicenseKeyDto] licenseKeyDto (required): - Future setUserLicenseWithHttpInfo(LicenseKeyDto licenseKeyDto,) async { + Future setUserLicenseWithHttpInfo(LicenseKeyDto licenseKeyDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/users/me/license'; @@ -592,6 +603,7 @@ class UsersApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -602,8 +614,8 @@ class UsersApi { /// Parameters: /// /// * [LicenseKeyDto] licenseKeyDto (required): - Future setUserLicense(LicenseKeyDto licenseKeyDto,) async { - final response = await setUserLicenseWithHttpInfo(licenseKeyDto,); + Future setUserLicense(LicenseKeyDto licenseKeyDto, { Future? abortTrigger, }) async { + final response = await setUserLicenseWithHttpInfo(licenseKeyDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -626,7 +638,7 @@ class UsersApi { /// Parameters: /// /// * [OnboardingDto] onboardingDto (required): - Future setUserOnboardingWithHttpInfo(OnboardingDto onboardingDto,) async { + Future setUserOnboardingWithHttpInfo(OnboardingDto onboardingDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/users/me/onboarding'; @@ -648,6 +660,7 @@ class UsersApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -658,8 +671,8 @@ class UsersApi { /// Parameters: /// /// * [OnboardingDto] onboardingDto (required): - Future setUserOnboarding(OnboardingDto onboardingDto,) async { - final response = await setUserOnboardingWithHttpInfo(onboardingDto,); + Future setUserOnboarding(OnboardingDto onboardingDto, { Future? abortTrigger, }) async { + final response = await setUserOnboardingWithHttpInfo(onboardingDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -682,7 +695,7 @@ class UsersApi { /// Parameters: /// /// * [UserPreferencesUpdateDto] userPreferencesUpdateDto (required): - Future updateMyPreferencesWithHttpInfo(UserPreferencesUpdateDto userPreferencesUpdateDto,) async { + Future updateMyPreferencesWithHttpInfo(UserPreferencesUpdateDto userPreferencesUpdateDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/users/me/preferences'; @@ -704,6 +717,7 @@ class UsersApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -714,8 +728,8 @@ class UsersApi { /// Parameters: /// /// * [UserPreferencesUpdateDto] userPreferencesUpdateDto (required): - Future updateMyPreferences(UserPreferencesUpdateDto userPreferencesUpdateDto,) async { - final response = await updateMyPreferencesWithHttpInfo(userPreferencesUpdateDto,); + Future updateMyPreferences(UserPreferencesUpdateDto userPreferencesUpdateDto, { Future? abortTrigger, }) async { + final response = await updateMyPreferencesWithHttpInfo(userPreferencesUpdateDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -738,7 +752,7 @@ class UsersApi { /// Parameters: /// /// * [UserUpdateMeDto] userUpdateMeDto (required): - Future updateMyUserWithHttpInfo(UserUpdateMeDto userUpdateMeDto,) async { + Future updateMyUserWithHttpInfo(UserUpdateMeDto userUpdateMeDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/users/me'; @@ -760,6 +774,7 @@ class UsersApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -770,8 +785,8 @@ class UsersApi { /// Parameters: /// /// * [UserUpdateMeDto] userUpdateMeDto (required): - Future updateMyUser(UserUpdateMeDto userUpdateMeDto,) async { - final response = await updateMyUserWithHttpInfo(userUpdateMeDto,); + Future updateMyUser(UserUpdateMeDto userUpdateMeDto, { Future? abortTrigger, }) async { + final response = await updateMyUserWithHttpInfo(userUpdateMeDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } diff --git a/mobile/openapi/lib/api/views_api.dart b/mobile/openapi/lib/api/views_api.dart index a45e89d58f..3ccbacb650 100644 --- a/mobile/openapi/lib/api/views_api.dart +++ b/mobile/openapi/lib/api/views_api.dart @@ -25,7 +25,7 @@ class ViewsApi { /// Parameters: /// /// * [String] path (required): - Future getAssetsByOriginalPathWithHttpInfo(String path,) async { + Future getAssetsByOriginalPathWithHttpInfo(String path, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/view/folder'; @@ -49,6 +49,7 @@ class ViewsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -59,8 +60,8 @@ class ViewsApi { /// Parameters: /// /// * [String] path (required): - Future?> getAssetsByOriginalPath(String path,) async { - final response = await getAssetsByOriginalPathWithHttpInfo(path,); + Future?> getAssetsByOriginalPath(String path, { Future? abortTrigger, }) async { + final response = await getAssetsByOriginalPathWithHttpInfo(path, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -82,7 +83,7 @@ class ViewsApi { /// Retrieve a list of unique folder paths from asset original paths. /// /// Note: This method returns the HTTP [Response]. - Future getUniqueOriginalPathsWithHttpInfo() async { + Future getUniqueOriginalPathsWithHttpInfo({ Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/view/folder/unique-paths'; @@ -104,14 +105,15 @@ class ViewsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } /// Retrieve unique paths /// /// Retrieve a list of unique folder paths from asset original paths. - Future?> getUniqueOriginalPaths() async { - final response = await getUniqueOriginalPathsWithHttpInfo(); + Future?> getUniqueOriginalPaths({ Future? abortTrigger, }) async { + final response = await getUniqueOriginalPathsWithHttpInfo(abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } diff --git a/mobile/openapi/lib/api/workflows_api.dart b/mobile/openapi/lib/api/workflows_api.dart index c589ec9823..4b27acd624 100644 --- a/mobile/openapi/lib/api/workflows_api.dart +++ b/mobile/openapi/lib/api/workflows_api.dart @@ -25,7 +25,7 @@ class WorkflowsApi { /// Parameters: /// /// * [WorkflowCreateDto] workflowCreateDto (required): - Future createWorkflowWithHttpInfo(WorkflowCreateDto workflowCreateDto,) async { + Future createWorkflowWithHttpInfo(WorkflowCreateDto workflowCreateDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/workflows'; @@ -47,6 +47,7 @@ class WorkflowsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -57,8 +58,8 @@ class WorkflowsApi { /// Parameters: /// /// * [WorkflowCreateDto] workflowCreateDto (required): - Future createWorkflow(WorkflowCreateDto workflowCreateDto,) async { - final response = await createWorkflowWithHttpInfo(workflowCreateDto,); + Future createWorkflow(WorkflowCreateDto workflowCreateDto, { Future? abortTrigger, }) async { + final response = await createWorkflowWithHttpInfo(workflowCreateDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -81,7 +82,7 @@ class WorkflowsApi { /// Parameters: /// /// * [String] id (required): - Future deleteWorkflowWithHttpInfo(String id,) async { + Future deleteWorkflowWithHttpInfo(String id, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/workflows/{id}' .replaceAll('{id}', id); @@ -104,6 +105,7 @@ class WorkflowsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -114,8 +116,8 @@ class WorkflowsApi { /// Parameters: /// /// * [String] id (required): - Future deleteWorkflow(String id,) async { - final response = await deleteWorkflowWithHttpInfo(id,); + Future deleteWorkflow(String id, { Future? abortTrigger, }) async { + final response = await deleteWorkflowWithHttpInfo(id, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -130,7 +132,7 @@ class WorkflowsApi { /// Parameters: /// /// * [String] id (required): - Future getWorkflowWithHttpInfo(String id,) async { + Future getWorkflowWithHttpInfo(String id, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/workflows/{id}' .replaceAll('{id}', id); @@ -153,6 +155,7 @@ class WorkflowsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -163,8 +166,8 @@ class WorkflowsApi { /// Parameters: /// /// * [String] id (required): - Future getWorkflow(String id,) async { - final response = await getWorkflowWithHttpInfo(id,); + Future getWorkflow(String id, { Future? abortTrigger, }) async { + final response = await getWorkflowWithHttpInfo(id, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -178,14 +181,19 @@ class WorkflowsApi { return null; } - /// List all workflows + /// Retrieve a workflow /// - /// Retrieve a list of workflows available to the authenticated user. + /// Retrieve a workflow details without ids, default values, etc. /// /// Note: This method returns the HTTP [Response]. - Future getWorkflowsWithHttpInfo() async { + /// + /// Parameters: + /// + /// * [String] id (required): + Future getWorkflowForShareWithHttpInfo(String id, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations - final apiPath = r'/workflows'; + final apiPath = r'/workflows/{id}/share' + .replaceAll('{id}', id); // ignore: prefer_final_locals Object? postBody; @@ -205,14 +213,170 @@ class WorkflowsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, + ); + } + + /// Retrieve a workflow + /// + /// Retrieve a workflow details without ids, default values, etc. + /// + /// Parameters: + /// + /// * [String] id (required): + Future getWorkflowForShare(String id, { Future? abortTrigger, }) async { + final response = await getWorkflowForShareWithHttpInfo(id, abortTrigger: abortTrigger,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'WorkflowShareResponseDto',) as WorkflowShareResponseDto; + + } + return null; + } + + /// List all workflow triggers + /// + /// Retrieve a list of all available workflow triggers. + /// + /// Note: This method returns the HTTP [Response]. + Future getWorkflowTriggersWithHttpInfo({ Future? abortTrigger, }) async { + // ignore: prefer_const_declarations + final apiPath = r'/workflows/triggers'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, + ); + } + + /// List all workflow triggers + /// + /// Retrieve a list of all available workflow triggers. + Future?> getWorkflowTriggers({ Future? abortTrigger, }) async { + final response = await getWorkflowTriggersWithHttpInfo(abortTrigger: abortTrigger,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// List all workflows + /// + /// Retrieve a list of workflows available to the authenticated user. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] description: + /// Workflow description + /// + /// * [bool] enabled: + /// Workflow enabled + /// + /// * [String] id: + /// Workflow ID + /// + /// * [String] name: + /// Workflow name + /// + /// * [WorkflowTrigger] trigger: + /// Workflow trigger type + Future searchWorkflowsWithHttpInfo({ String? description, bool? enabled, String? id, String? name, WorkflowTrigger? trigger, Future? abortTrigger, }) async { + // ignore: prefer_const_declarations + final apiPath = r'/workflows'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + if (description != null) { + queryParams.addAll(_queryParams('', 'description', description)); + } + if (enabled != null) { + queryParams.addAll(_queryParams('', 'enabled', enabled)); + } + if (id != null) { + queryParams.addAll(_queryParams('', 'id', id)); + } + if (name != null) { + queryParams.addAll(_queryParams('', 'name', name)); + } + if (trigger != null) { + queryParams.addAll(_queryParams('', 'trigger', trigger)); + } + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } /// List all workflows /// /// Retrieve a list of workflows available to the authenticated user. - Future?> getWorkflows() async { - final response = await getWorkflowsWithHttpInfo(); + /// + /// Parameters: + /// + /// * [String] description: + /// Workflow description + /// + /// * [bool] enabled: + /// Workflow enabled + /// + /// * [String] id: + /// Workflow ID + /// + /// * [String] name: + /// Workflow name + /// + /// * [WorkflowTrigger] trigger: + /// Workflow trigger type + Future?> searchWorkflows({ String? description, bool? enabled, String? id, String? name, WorkflowTrigger? trigger, Future? abortTrigger, }) async { + final response = await searchWorkflowsWithHttpInfo(description: description, enabled: enabled, id: id, name: name, trigger: trigger, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -240,7 +404,7 @@ class WorkflowsApi { /// * [String] id (required): /// /// * [WorkflowUpdateDto] workflowUpdateDto (required): - Future updateWorkflowWithHttpInfo(String id, WorkflowUpdateDto workflowUpdateDto,) async { + Future updateWorkflowWithHttpInfo(String id, WorkflowUpdateDto workflowUpdateDto, { Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/workflows/{id}' .replaceAll('{id}', id); @@ -263,6 +427,7 @@ class WorkflowsApi { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -275,8 +440,8 @@ class WorkflowsApi { /// * [String] id (required): /// /// * [WorkflowUpdateDto] workflowUpdateDto (required): - Future updateWorkflow(String id, WorkflowUpdateDto workflowUpdateDto,) async { - final response = await updateWorkflowWithHttpInfo(id, workflowUpdateDto,); + Future updateWorkflow(String id, WorkflowUpdateDto workflowUpdateDto, { Future? abortTrigger, }) async { + final response = await updateWorkflowWithHttpInfo(id, workflowUpdateDto, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } diff --git a/mobile/openapi/lib/api_client.dart b/mobile/openapi/lib/api_client.dart index a913655354..6e426b8ec9 100644 --- a/mobile/openapi/lib/api_client.dart +++ b/mobile/openapi/lib/api_client.dart @@ -13,7 +13,7 @@ part of openapi.api; class ApiClient { ApiClient({this.basePath = '/api', this.authentication,}); - final String basePath; + String basePath; final Authentication? authentication; var _client = Client(); @@ -44,8 +44,9 @@ class ApiClient { Object? body, Map headerParams, Map formParams, - String? contentType, - ) async { + String? contentType, { + Future? abortTrigger, + }) async { await authentication?.applyToParams(queryParams, headerParams); headerParams.addAll(_defaultHeaderMap); @@ -63,7 +64,7 @@ class ApiClient { body is MultipartFile && (contentType == null || !contentType.toLowerCase().startsWith('multipart/form-data')) ) { - final request = StreamedRequest(method, uri); + final request = AbortableStreamedRequest(method, uri, abortTrigger: abortTrigger); request.headers.addAll(headerParams); request.contentLength = body.length; body.finalize().listen( @@ -78,7 +79,7 @@ class ApiClient { } if (body is MultipartRequest) { - final request = MultipartRequest(method, uri); + final request = AbortableMultipartRequest(method, uri, abortTrigger: abortTrigger); request.fields.addAll(body.fields); request.files.addAll(body.files); request.headers.addAll(body.headers); @@ -92,14 +93,19 @@ class ApiClient { : await serializeAsync(body); final nullableHeaderParams = headerParams.isEmpty ? null : headerParams; - switch(method) { - case 'POST': return await _client.post(uri, headers: nullableHeaderParams, body: msgBody,); - case 'PUT': return await _client.put(uri, headers: nullableHeaderParams, body: msgBody,); - case 'DELETE': return await _client.delete(uri, headers: nullableHeaderParams, body: msgBody,); - case 'PATCH': return await _client.patch(uri, headers: nullableHeaderParams, body: msgBody,); - case 'HEAD': return await _client.head(uri, headers: nullableHeaderParams,); - case 'GET': return await _client.get(uri, headers: nullableHeaderParams,); + final request = AbortableRequest(method, uri, abortTrigger: abortTrigger); + if (nullableHeaderParams != null) { + request.headers.addAll(nullableHeaderParams); } + if (msgBody is String) { + request.body = msgBody; + } else if (msgBody is List) { + request.bodyBytes = msgBody; + } else if (msgBody is Map) { + request.bodyFields = msgBody; + } + final response = await _client.send(request); + return Response.fromStream(response); } on SocketException catch (error, trace) { throw ApiException.withInner( HttpStatus.badRequest, @@ -136,11 +142,6 @@ class ApiClient { trace, ); } - - throw ApiException( - HttpStatus.badRequest, - 'Invalid HTTP operation: $method $path', - ); } Future deserializeAsync(String value, String targetType, {bool growable = false,}) => @@ -256,8 +257,6 @@ class ApiClient { return AssetFaceUpdateDto.fromJson(value); case 'AssetFaceUpdateItem': return AssetFaceUpdateItem.fromJson(value); - case 'AssetFaceWithoutPersonResponseDto': - return AssetFaceWithoutPersonResponseDto.fromJson(value); case 'AssetIdErrorReason': return AssetIdErrorReasonTypeTransformer().decode(value); case 'AssetIdsDto': @@ -294,6 +293,8 @@ class ApiClient { return AssetOcrResponseDto.fromJson(value); case 'AssetOrder': return AssetOrderTypeTransformer().decode(value); + case 'AssetOrderBy': + return AssetOrderByTypeTransformer().decode(value); case 'AssetRejectReason': return AssetRejectReasonTypeTransformer().decode(value); case 'AssetResponseDto': @@ -508,8 +509,6 @@ class ApiClient { return PersonStatisticsResponseDto.fromJson(value); case 'PersonUpdateDto': return PersonUpdateDto.fromJson(value); - case 'PersonWithFacesResponseDto': - return PersonWithFacesResponseDto.fromJson(value); case 'PinCodeChangeDto': return PinCodeChangeDto.fromJson(value); case 'PinCodeResetDto': @@ -518,26 +517,14 @@ class ApiClient { return PinCodeSetupDto.fromJson(value); case 'PlacesResponseDto': return PlacesResponseDto.fromJson(value); - case 'PluginActionResponseDto': - return PluginActionResponseDto.fromJson(value); - case 'PluginContextType': - return PluginContextTypeTypeTransformer().decode(value); - case 'PluginFilterResponseDto': - return PluginFilterResponseDto.fromJson(value); - case 'PluginJsonSchema': - return PluginJsonSchema.fromJson(value); - case 'PluginJsonSchemaProperty': - return PluginJsonSchemaProperty.fromJson(value); - case 'PluginJsonSchemaPropertyAdditionalProperties': - return PluginJsonSchemaPropertyAdditionalProperties.fromJson(value); - case 'PluginJsonSchemaType': - return PluginJsonSchemaTypeTypeTransformer().decode(value); + case 'PluginMethodResponseDto': + return PluginMethodResponseDto.fromJson(value); case 'PluginResponseDto': return PluginResponseDto.fromJson(value); - case 'PluginTriggerResponseDto': - return PluginTriggerResponseDto.fromJson(value); - case 'PluginTriggerType': - return PluginTriggerTypeTypeTransformer().decode(value); + case 'PluginTemplateResponseDto': + return PluginTemplateResponseDto.fromJson(value); + case 'PluginTemplateStepResponseDto': + return PluginTemplateStepResponseDto.fromJson(value); case 'PurchaseResponse': return PurchaseResponse.fromJson(value); case 'PurchaseUpdate': @@ -576,6 +563,12 @@ class ApiClient { return ReactionLevelTypeTransformer().decode(value); case 'ReactionType': return ReactionTypeTypeTransformer().decode(value); + case 'ReleaseChannel': + return ReleaseChannelTypeTransformer().decode(value); + case 'ReleaseEventV1': + return ReleaseEventV1.fromJson(value); + case 'ReleaseType': + return ReleaseTypeTypeTransformer().decode(value); case 'ReverseGeocodingStateResponseDto': return ReverseGeocodingStateResponseDto.fromJson(value); case 'RotateParameters': @@ -704,6 +697,8 @@ class ApiClient { return SyncAssetOcrV1.fromJson(value); case 'SyncAssetV1': return SyncAssetV1.fromJson(value); + case 'SyncAssetV2': + return SyncAssetV2.fromJson(value); case 'SyncAuthUserV1': return SyncAuthUserV1.fromJson(value); case 'SyncEntityType': @@ -746,6 +741,8 @@ class ApiClient { return SystemConfigDto.fromJson(value); case 'SystemConfigFFmpegDto': return SystemConfigFFmpegDto.fromJson(value); + case 'SystemConfigFFmpegRealtimeDto': + return SystemConfigFFmpegRealtimeDto.fromJson(value); case 'SystemConfigFacesDto': return SystemConfigFacesDto.fromJson(value); case 'SystemConfigGeneratedFullsizeImageDto': @@ -884,18 +881,22 @@ class ApiClient { return VideoCodecTypeTransformer().decode(value); case 'VideoContainer': return VideoContainerTypeTransformer().decode(value); - case 'WorkflowActionItemDto': - return WorkflowActionItemDto.fromJson(value); - case 'WorkflowActionResponseDto': - return WorkflowActionResponseDto.fromJson(value); case 'WorkflowCreateDto': return WorkflowCreateDto.fromJson(value); - case 'WorkflowFilterItemDto': - return WorkflowFilterItemDto.fromJson(value); - case 'WorkflowFilterResponseDto': - return WorkflowFilterResponseDto.fromJson(value); case 'WorkflowResponseDto': return WorkflowResponseDto.fromJson(value); + case 'WorkflowShareResponseDto': + return WorkflowShareResponseDto.fromJson(value); + case 'WorkflowShareStepDto': + return WorkflowShareStepDto.fromJson(value); + case 'WorkflowStepDto': + return WorkflowStepDto.fromJson(value); + case 'WorkflowTrigger': + return WorkflowTriggerTypeTransformer().decode(value); + case 'WorkflowTriggerResponseDto': + return WorkflowTriggerResponseDto.fromJson(value); + case 'WorkflowType': + return WorkflowTypeTypeTransformer().decode(value); case 'WorkflowUpdateDto': return WorkflowUpdateDto.fromJson(value); default: diff --git a/mobile/openapi/lib/api_helper.dart b/mobile/openapi/lib/api_helper.dart index 3b36b23d6c..6c824d4a86 100644 --- a/mobile/openapi/lib/api_helper.dart +++ b/mobile/openapi/lib/api_helper.dart @@ -76,6 +76,9 @@ String parameterToString(dynamic value) { if (value is AssetOrder) { return AssetOrderTypeTransformer().encode(value).toString(); } + if (value is AssetOrderBy) { + return AssetOrderByTypeTransformer().encode(value).toString(); + } if (value is AssetRejectReason) { return AssetRejectReasonTypeTransformer().encode(value).toString(); } @@ -139,15 +142,6 @@ String parameterToString(dynamic value) { if (value is Permission) { return PermissionTypeTransformer().encode(value).toString(); } - if (value is PluginContextType) { - return PluginContextTypeTypeTransformer().encode(value).toString(); - } - if (value is PluginJsonSchemaType) { - return PluginJsonSchemaTypeTypeTransformer().encode(value).toString(); - } - if (value is PluginTriggerType) { - return PluginTriggerTypeTypeTransformer().encode(value).toString(); - } if (value is QueueCommand) { return QueueCommandTypeTransformer().encode(value).toString(); } @@ -163,6 +157,12 @@ String parameterToString(dynamic value) { if (value is ReactionType) { return ReactionTypeTypeTransformer().encode(value).toString(); } + if (value is ReleaseChannel) { + return ReleaseChannelTypeTransformer().encode(value).toString(); + } + if (value is ReleaseType) { + return ReleaseTypeTypeTransformer().encode(value).toString(); + } if (value is SearchSuggestionType) { return SearchSuggestionTypeTypeTransformer().encode(value).toString(); } @@ -205,6 +205,12 @@ String parameterToString(dynamic value) { if (value is VideoContainer) { return VideoContainerTypeTransformer().encode(value).toString(); } + if (value is WorkflowTrigger) { + return WorkflowTriggerTypeTransformer().encode(value).toString(); + } + if (value is WorkflowType) { + return WorkflowTypeTypeTransformer().encode(value).toString(); + } return value.toString(); } @@ -220,6 +226,9 @@ Future _decodeBodyBytes(Response response) async { /// Returns a valid [T] value found at the specified Map [key], null otherwise. T? mapValueOfType(dynamic map, String key) { final dynamic value = map is Map ? map[key] : null; + if (T == double && value is int) { + return value.toDouble() as T; + } return value is T ? value : null; } diff --git a/mobile/openapi/lib/model/activity_create_dto.dart b/mobile/openapi/lib/model/activity_create_dto.dart index bc220e64ce..7dbea342c9 100644 --- a/mobile/openapi/lib/model/activity_create_dto.dart +++ b/mobile/openapi/lib/model/activity_create_dto.dart @@ -14,8 +14,8 @@ class ActivityCreateDto { /// Returns a new [ActivityCreateDto] instance. ActivityCreateDto({ required this.albumId, - this.assetId, - this.comment, + this.assetId = const Optional.absent(), + this.comment = const Optional.absent(), required this.type, }); @@ -29,7 +29,7 @@ class ActivityCreateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? assetId; + Optional assetId; /// Comment text (required if type is comment) /// @@ -38,7 +38,7 @@ class ActivityCreateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? comment; + Optional comment; ReactionType type; @@ -63,15 +63,13 @@ class ActivityCreateDto { Map toJson() { final json = {}; json[r'albumId'] = this.albumId; - if (this.assetId != null) { - json[r'assetId'] = this.assetId; - } else { - // json[r'assetId'] = null; + if (this.assetId.isPresent) { + final value = this.assetId.value; + json[r'assetId'] = value; } - if (this.comment != null) { - json[r'comment'] = this.comment; - } else { - // json[r'comment'] = null; + if (this.comment.isPresent) { + final value = this.comment.value; + json[r'comment'] = value; } json[r'type'] = this.type; return json; @@ -87,8 +85,8 @@ class ActivityCreateDto { return ActivityCreateDto( albumId: mapValueOfType(json, r'albumId')!, - assetId: mapValueOfType(json, r'assetId'), - comment: mapValueOfType(json, r'comment'), + assetId: json.containsKey(r'assetId') ? Optional.present(mapValueOfType(json, r'assetId')) : const Optional.absent(), + comment: json.containsKey(r'comment') ? Optional.present(mapValueOfType(json, r'comment')) : const Optional.absent(), type: ReactionType.fromJson(json[r'type'])!, ); } diff --git a/mobile/openapi/lib/model/activity_response_dto.dart b/mobile/openapi/lib/model/activity_response_dto.dart index 1b0e279ab7..8a8f807260 100644 --- a/mobile/openapi/lib/model/activity_response_dto.dart +++ b/mobile/openapi/lib/model/activity_response_dto.dart @@ -14,7 +14,7 @@ class ActivityResponseDto { /// Returns a new [ActivityResponseDto] instance. ActivityResponseDto({ required this.assetId, - this.comment, + this.comment = const Optional.absent(), required this.createdAt, required this.id, required this.type, @@ -25,7 +25,7 @@ class ActivityResponseDto { String? assetId; /// Comment text (for comment activities) - String? comment; + Optional comment; /// Creation date DateTime createdAt; @@ -64,12 +64,11 @@ class ActivityResponseDto { if (this.assetId != null) { json[r'assetId'] = this.assetId; } else { - // json[r'assetId'] = null; + json[r'assetId'] = null; } - if (this.comment != null) { - json[r'comment'] = this.comment; - } else { - // json[r'comment'] = null; + if (this.comment.isPresent) { + final value = this.comment.value; + json[r'comment'] = value; } json[r'createdAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') ? this.createdAt.millisecondsSinceEpoch @@ -90,7 +89,7 @@ class ActivityResponseDto { return ActivityResponseDto( assetId: mapValueOfType(json, r'assetId'), - comment: mapValueOfType(json, r'comment'), + comment: json.containsKey(r'comment') ? Optional.present(mapValueOfType(json, r'comment')) : const Optional.absent(), createdAt: mapDateTime(json, r'createdAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')!, id: mapValueOfType(json, r'id')!, type: ReactionType.fromJson(json[r'type'])!, diff --git a/mobile/openapi/lib/model/album_response_dto.dart b/mobile/openapi/lib/model/album_response_dto.dart index fd90f23e3a..a7e350fd53 100644 --- a/mobile/openapi/lib/model/album_response_dto.dart +++ b/mobile/openapi/lib/model/album_response_dto.dart @@ -17,17 +17,17 @@ class AlbumResponseDto { required this.albumThumbnailAssetId, this.albumUsers = const [], required this.assetCount, - this.contributorCounts = const [], + this.contributorCounts = const Optional.present(const []), required this.createdAt, required this.description, - this.endDate, + this.endDate = const Optional.absent(), required this.hasSharedLink, required this.id, required this.isActivityEnabled, - this.lastModifiedAssetTimestamp, - this.order, + this.lastModifiedAssetTimestamp = const Optional.absent(), + this.order = const Optional.absent(), required this.shared, - this.startDate, + this.startDate = const Optional.absent(), required this.updatedAt, }); @@ -46,7 +46,7 @@ class AlbumResponseDto { /// Maximum value: 9007199254740991 int assetCount; - List contributorCounts; + Optional?> contributorCounts; /// Creation date DateTime createdAt; @@ -61,7 +61,7 @@ class AlbumResponseDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - DateTime? endDate; + Optional endDate; /// Has shared link bool hasSharedLink; @@ -79,7 +79,7 @@ class AlbumResponseDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - DateTime? lastModifiedAssetTimestamp; + Optional lastModifiedAssetTimestamp; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -87,7 +87,7 @@ class AlbumResponseDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - AssetOrder? order; + Optional order; /// Is shared album bool shared; @@ -99,7 +99,7 @@ class AlbumResponseDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - DateTime? startDate; + Optional startDate; /// Last update date DateTime updatedAt; @@ -152,36 +152,35 @@ class AlbumResponseDto { if (this.albumThumbnailAssetId != null) { json[r'albumThumbnailAssetId'] = this.albumThumbnailAssetId; } else { - // json[r'albumThumbnailAssetId'] = null; + json[r'albumThumbnailAssetId'] = null; } json[r'albumUsers'] = this.albumUsers; json[r'assetCount'] = this.assetCount; - json[r'contributorCounts'] = this.contributorCounts; + if (this.contributorCounts.isPresent) { + final value = this.contributorCounts.value; + json[r'contributorCounts'] = value; + } json[r'createdAt'] = this.createdAt.toUtc().toIso8601String(); json[r'description'] = this.description; - if (this.endDate != null) { - json[r'endDate'] = this.endDate!.toUtc().toIso8601String(); - } else { - // json[r'endDate'] = null; + if (this.endDate.isPresent) { + final value = this.endDate.value; + json[r'endDate'] = value == null ? null : value.toUtc().toIso8601String(); } json[r'hasSharedLink'] = this.hasSharedLink; json[r'id'] = this.id; json[r'isActivityEnabled'] = this.isActivityEnabled; - if (this.lastModifiedAssetTimestamp != null) { - json[r'lastModifiedAssetTimestamp'] = this.lastModifiedAssetTimestamp!.toUtc().toIso8601String(); - } else { - // json[r'lastModifiedAssetTimestamp'] = null; + if (this.lastModifiedAssetTimestamp.isPresent) { + final value = this.lastModifiedAssetTimestamp.value; + json[r'lastModifiedAssetTimestamp'] = value == null ? null : value.toUtc().toIso8601String(); } - if (this.order != null) { - json[r'order'] = this.order; - } else { - // json[r'order'] = null; + if (this.order.isPresent) { + final value = this.order.value; + json[r'order'] = value; } json[r'shared'] = this.shared; - if (this.startDate != null) { - json[r'startDate'] = this.startDate!.toUtc().toIso8601String(); - } else { - // json[r'startDate'] = null; + if (this.startDate.isPresent) { + final value = this.startDate.value; + json[r'startDate'] = value == null ? null : value.toUtc().toIso8601String(); } json[r'updatedAt'] = this.updatedAt.toUtc().toIso8601String(); return json; @@ -200,17 +199,17 @@ class AlbumResponseDto { albumThumbnailAssetId: mapValueOfType(json, r'albumThumbnailAssetId'), albumUsers: AlbumUserResponseDto.listFromJson(json[r'albumUsers']), assetCount: mapValueOfType(json, r'assetCount')!, - contributorCounts: ContributorCountResponseDto.listFromJson(json[r'contributorCounts']), + contributorCounts: json.containsKey(r'contributorCounts') ? Optional.present(ContributorCountResponseDto.listFromJson(json[r'contributorCounts'])) : const Optional.absent(), createdAt: mapDateTime(json, r'createdAt', r'')!, description: mapValueOfType(json, r'description')!, - endDate: mapDateTime(json, r'endDate', r''), + endDate: json.containsKey(r'endDate') ? Optional.present(mapDateTime(json, r'endDate', r'')) : const Optional.absent(), hasSharedLink: mapValueOfType(json, r'hasSharedLink')!, id: mapValueOfType(json, r'id')!, isActivityEnabled: mapValueOfType(json, r'isActivityEnabled')!, - lastModifiedAssetTimestamp: mapDateTime(json, r'lastModifiedAssetTimestamp', r''), - order: AssetOrder.fromJson(json[r'order']), + lastModifiedAssetTimestamp: json.containsKey(r'lastModifiedAssetTimestamp') ? Optional.present(mapDateTime(json, r'lastModifiedAssetTimestamp', r'')) : const Optional.absent(), + order: json.containsKey(r'order') ? Optional.present(AssetOrder.fromJson(json[r'order'])) : const Optional.absent(), shared: mapValueOfType(json, r'shared')!, - startDate: mapDateTime(json, r'startDate', r''), + startDate: json.containsKey(r'startDate') ? Optional.present(mapDateTime(json, r'startDate', r'')) : const Optional.absent(), updatedAt: mapDateTime(json, r'updatedAt', r'')!, ); } diff --git a/mobile/openapi/lib/model/album_user_add_dto.dart b/mobile/openapi/lib/model/album_user_add_dto.dart index ee457905bd..e47ffc421c 100644 --- a/mobile/openapi/lib/model/album_user_add_dto.dart +++ b/mobile/openapi/lib/model/album_user_add_dto.dart @@ -13,7 +13,7 @@ part of openapi.api; class AlbumUserAddDto { /// Returns a new [AlbumUserAddDto] instance. AlbumUserAddDto({ - this.role, + this.role = const Optional.absent(), required this.userId, }); @@ -23,7 +23,7 @@ class AlbumUserAddDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - AlbumUserRole? role; + Optional role; /// User ID String userId; @@ -44,10 +44,9 @@ class AlbumUserAddDto { Map toJson() { final json = {}; - if (this.role != null) { - json[r'role'] = this.role; - } else { - // json[r'role'] = null; + if (this.role.isPresent) { + final value = this.role.value; + json[r'role'] = value; } json[r'userId'] = this.userId; return json; @@ -62,7 +61,7 @@ class AlbumUserAddDto { final json = value.cast(); return AlbumUserAddDto( - role: AlbumUserRole.fromJson(json[r'role']), + role: json.containsKey(r'role') ? Optional.present(AlbumUserRole.fromJson(json[r'role'])) : const Optional.absent(), userId: mapValueOfType(json, r'userId')!, ); } diff --git a/mobile/openapi/lib/model/albums_add_assets_response_dto.dart b/mobile/openapi/lib/model/albums_add_assets_response_dto.dart index 99e679222e..943bd30bc5 100644 --- a/mobile/openapi/lib/model/albums_add_assets_response_dto.dart +++ b/mobile/openapi/lib/model/albums_add_assets_response_dto.dart @@ -13,7 +13,7 @@ part of openapi.api; class AlbumsAddAssetsResponseDto { /// Returns a new [AlbumsAddAssetsResponseDto] instance. AlbumsAddAssetsResponseDto({ - this.error, + this.error = const Optional.absent(), required this.success, }); @@ -23,7 +23,7 @@ class AlbumsAddAssetsResponseDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - BulkIdErrorReason? error; + Optional error; /// Operation success bool success; @@ -44,10 +44,9 @@ class AlbumsAddAssetsResponseDto { Map toJson() { final json = {}; - if (this.error != null) { - json[r'error'] = this.error; - } else { - // json[r'error'] = null; + if (this.error.isPresent) { + final value = this.error.value; + json[r'error'] = value; } json[r'success'] = this.success; return json; @@ -62,7 +61,7 @@ class AlbumsAddAssetsResponseDto { final json = value.cast(); return AlbumsAddAssetsResponseDto( - error: BulkIdErrorReason.fromJson(json[r'error']), + error: json.containsKey(r'error') ? Optional.present(BulkIdErrorReason.fromJson(json[r'error'])) : const Optional.absent(), success: mapValueOfType(json, r'success')!, ); } diff --git a/mobile/openapi/lib/model/albums_update.dart b/mobile/openapi/lib/model/albums_update.dart index d61b5c1398..46bb3ef66d 100644 --- a/mobile/openapi/lib/model/albums_update.dart +++ b/mobile/openapi/lib/model/albums_update.dart @@ -13,7 +13,7 @@ part of openapi.api; class AlbumsUpdate { /// Returns a new [AlbumsUpdate] instance. AlbumsUpdate({ - this.defaultAssetOrder, + this.defaultAssetOrder = const Optional.absent(), }); /// @@ -22,7 +22,7 @@ class AlbumsUpdate { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - AssetOrder? defaultAssetOrder; + Optional defaultAssetOrder; @override bool operator ==(Object other) => identical(this, other) || other is AlbumsUpdate && @@ -38,10 +38,9 @@ class AlbumsUpdate { Map toJson() { final json = {}; - if (this.defaultAssetOrder != null) { - json[r'defaultAssetOrder'] = this.defaultAssetOrder; - } else { - // json[r'defaultAssetOrder'] = null; + if (this.defaultAssetOrder.isPresent) { + final value = this.defaultAssetOrder.value; + json[r'defaultAssetOrder'] = value; } return json; } @@ -55,7 +54,7 @@ class AlbumsUpdate { final json = value.cast(); return AlbumsUpdate( - defaultAssetOrder: AssetOrder.fromJson(json[r'defaultAssetOrder']), + defaultAssetOrder: json.containsKey(r'defaultAssetOrder') ? Optional.present(AssetOrder.fromJson(json[r'defaultAssetOrder'])) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/api_key_create_dto.dart b/mobile/openapi/lib/model/api_key_create_dto.dart index 6d3ffc1eb1..e1a50fecd5 100644 --- a/mobile/openapi/lib/model/api_key_create_dto.dart +++ b/mobile/openapi/lib/model/api_key_create_dto.dart @@ -13,7 +13,7 @@ part of openapi.api; class ApiKeyCreateDto { /// Returns a new [ApiKeyCreateDto] instance. ApiKeyCreateDto({ - this.name, + this.name = const Optional.absent(), this.permissions = const [], }); @@ -24,7 +24,7 @@ class ApiKeyCreateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? name; + Optional name; /// List of permissions List permissions; @@ -45,10 +45,9 @@ class ApiKeyCreateDto { Map toJson() { final json = {}; - if (this.name != null) { - json[r'name'] = this.name; - } else { - // json[r'name'] = null; + if (this.name.isPresent) { + final value = this.name.value; + json[r'name'] = value; } json[r'permissions'] = this.permissions; return json; @@ -63,7 +62,7 @@ class ApiKeyCreateDto { final json = value.cast(); return ApiKeyCreateDto( - name: mapValueOfType(json, r'name'), + name: json.containsKey(r'name') ? Optional.present(mapValueOfType(json, r'name')) : const Optional.absent(), permissions: Permission.listFromJson(json[r'permissions']), ); } diff --git a/mobile/openapi/lib/model/api_key_update_dto.dart b/mobile/openapi/lib/model/api_key_update_dto.dart index c8df4be654..c6b0b5ed3c 100644 --- a/mobile/openapi/lib/model/api_key_update_dto.dart +++ b/mobile/openapi/lib/model/api_key_update_dto.dart @@ -13,8 +13,8 @@ part of openapi.api; class ApiKeyUpdateDto { /// Returns a new [ApiKeyUpdateDto] instance. ApiKeyUpdateDto({ - this.name, - this.permissions = const [], + this.name = const Optional.absent(), + this.permissions = const Optional.present(const []), }); /// API key name @@ -24,10 +24,10 @@ class ApiKeyUpdateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? name; + Optional name; /// List of permissions - List permissions; + Optional?> permissions; @override bool operator ==(Object other) => identical(this, other) || other is ApiKeyUpdateDto && @@ -45,12 +45,14 @@ class ApiKeyUpdateDto { Map toJson() { final json = {}; - if (this.name != null) { - json[r'name'] = this.name; - } else { - // json[r'name'] = null; + if (this.name.isPresent) { + final value = this.name.value; + json[r'name'] = value; + } + if (this.permissions.isPresent) { + final value = this.permissions.value; + json[r'permissions'] = value; } - json[r'permissions'] = this.permissions; return json; } @@ -63,8 +65,8 @@ class ApiKeyUpdateDto { final json = value.cast(); return ApiKeyUpdateDto( - name: mapValueOfType(json, r'name'), - permissions: Permission.listFromJson(json[r'permissions']), + name: json.containsKey(r'name') ? Optional.present(mapValueOfType(json, r'name')) : const Optional.absent(), + permissions: json.containsKey(r'permissions') ? Optional.present(Permission.listFromJson(json[r'permissions'])) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/asset_bulk_delete_dto.dart b/mobile/openapi/lib/model/asset_bulk_delete_dto.dart index 055ef16015..bfe51e1779 100644 --- a/mobile/openapi/lib/model/asset_bulk_delete_dto.dart +++ b/mobile/openapi/lib/model/asset_bulk_delete_dto.dart @@ -13,7 +13,7 @@ part of openapi.api; class AssetBulkDeleteDto { /// Returns a new [AssetBulkDeleteDto] instance. AssetBulkDeleteDto({ - this.force, + this.force = const Optional.absent(), this.ids = const [], }); @@ -24,7 +24,7 @@ class AssetBulkDeleteDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? force; + Optional force; /// IDs to process List ids; @@ -45,10 +45,9 @@ class AssetBulkDeleteDto { Map toJson() { final json = {}; - if (this.force != null) { - json[r'force'] = this.force; - } else { - // json[r'force'] = null; + if (this.force.isPresent) { + final value = this.force.value; + json[r'force'] = value; } json[r'ids'] = this.ids; return json; @@ -63,7 +62,7 @@ class AssetBulkDeleteDto { final json = value.cast(); return AssetBulkDeleteDto( - force: mapValueOfType(json, r'force'), + force: json.containsKey(r'force') ? Optional.present(mapValueOfType(json, r'force')) : const Optional.absent(), ids: json[r'ids'] is Iterable ? (json[r'ids'] as Iterable).cast().toList(growable: false) : const [], diff --git a/mobile/openapi/lib/model/asset_bulk_update_dto.dart b/mobile/openapi/lib/model/asset_bulk_update_dto.dart index f85026f054..4ba1546c44 100644 --- a/mobile/openapi/lib/model/asset_bulk_update_dto.dart +++ b/mobile/openapi/lib/model/asset_bulk_update_dto.dart @@ -13,17 +13,17 @@ part of openapi.api; class AssetBulkUpdateDto { /// Returns a new [AssetBulkUpdateDto] instance. AssetBulkUpdateDto({ - this.dateTimeOriginal, - this.dateTimeRelative, - this.description, - this.duplicateId, + this.dateTimeOriginal = const Optional.absent(), + this.dateTimeRelative = const Optional.absent(), + this.description = const Optional.absent(), + this.duplicateId = const Optional.absent(), this.ids = const [], - this.isFavorite, - this.latitude, - this.longitude, - this.rating, - this.timeZone, - this.visibility, + this.isFavorite = const Optional.absent(), + this.latitude = const Optional.absent(), + this.longitude = const Optional.absent(), + this.rating = const Optional.absent(), + this.timeZone = const Optional.absent(), + this.visibility = const Optional.absent(), }); /// Original date and time @@ -33,7 +33,7 @@ class AssetBulkUpdateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? dateTimeOriginal; + Optional dateTimeOriginal; /// Relative time offset in seconds /// @@ -45,7 +45,7 @@ class AssetBulkUpdateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - int? dateTimeRelative; + Optional dateTimeRelative; /// Asset description /// @@ -54,10 +54,10 @@ class AssetBulkUpdateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? description; + Optional description; /// Duplicate ID - String? duplicateId; + Optional duplicateId; /// Asset IDs to update List ids; @@ -69,7 +69,7 @@ class AssetBulkUpdateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? isFavorite; + Optional isFavorite; /// Latitude coordinate /// @@ -81,7 +81,7 @@ class AssetBulkUpdateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - num? latitude; + Optional latitude; /// Longitude coordinate /// @@ -93,13 +93,13 @@ class AssetBulkUpdateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - num? longitude; + Optional longitude; /// Rating in range [1-5], or null for unrated /// /// Minimum value: -1 /// Maximum value: 5 - int? rating; + Optional rating; /// Time zone (IANA timezone) /// @@ -108,7 +108,7 @@ class AssetBulkUpdateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? timeZone; + Optional timeZone; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -116,7 +116,7 @@ class AssetBulkUpdateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - AssetVisibility? visibility; + Optional visibility; @override bool operator ==(Object other) => identical(this, other) || other is AssetBulkUpdateDto && @@ -152,56 +152,46 @@ class AssetBulkUpdateDto { Map toJson() { final json = {}; - if (this.dateTimeOriginal != null) { - json[r'dateTimeOriginal'] = this.dateTimeOriginal; - } else { - // json[r'dateTimeOriginal'] = null; + if (this.dateTimeOriginal.isPresent) { + final value = this.dateTimeOriginal.value; + json[r'dateTimeOriginal'] = value; } - if (this.dateTimeRelative != null) { - json[r'dateTimeRelative'] = this.dateTimeRelative; - } else { - // json[r'dateTimeRelative'] = null; + if (this.dateTimeRelative.isPresent) { + final value = this.dateTimeRelative.value; + json[r'dateTimeRelative'] = value; } - if (this.description != null) { - json[r'description'] = this.description; - } else { - // json[r'description'] = null; + if (this.description.isPresent) { + final value = this.description.value; + json[r'description'] = value; } - if (this.duplicateId != null) { - json[r'duplicateId'] = this.duplicateId; - } else { - // json[r'duplicateId'] = null; + if (this.duplicateId.isPresent) { + final value = this.duplicateId.value; + json[r'duplicateId'] = value; } json[r'ids'] = this.ids; - if (this.isFavorite != null) { - json[r'isFavorite'] = this.isFavorite; - } else { - // json[r'isFavorite'] = null; + if (this.isFavorite.isPresent) { + final value = this.isFavorite.value; + json[r'isFavorite'] = value; } - if (this.latitude != null) { - json[r'latitude'] = this.latitude; - } else { - // json[r'latitude'] = null; + if (this.latitude.isPresent) { + final value = this.latitude.value; + json[r'latitude'] = value; } - if (this.longitude != null) { - json[r'longitude'] = this.longitude; - } else { - // json[r'longitude'] = null; + if (this.longitude.isPresent) { + final value = this.longitude.value; + json[r'longitude'] = value; } - if (this.rating != null) { - json[r'rating'] = this.rating; - } else { - // json[r'rating'] = null; + if (this.rating.isPresent) { + final value = this.rating.value; + json[r'rating'] = value; } - if (this.timeZone != null) { - json[r'timeZone'] = this.timeZone; - } else { - // json[r'timeZone'] = null; + if (this.timeZone.isPresent) { + final value = this.timeZone.value; + json[r'timeZone'] = value; } - if (this.visibility != null) { - json[r'visibility'] = this.visibility; - } else { - // json[r'visibility'] = null; + if (this.visibility.isPresent) { + final value = this.visibility.value; + json[r'visibility'] = value; } return json; } @@ -215,19 +205,19 @@ class AssetBulkUpdateDto { final json = value.cast(); return AssetBulkUpdateDto( - dateTimeOriginal: mapValueOfType(json, r'dateTimeOriginal'), - dateTimeRelative: mapValueOfType(json, r'dateTimeRelative'), - description: mapValueOfType(json, r'description'), - duplicateId: mapValueOfType(json, r'duplicateId'), + dateTimeOriginal: json.containsKey(r'dateTimeOriginal') ? Optional.present(mapValueOfType(json, r'dateTimeOriginal')) : const Optional.absent(), + dateTimeRelative: json.containsKey(r'dateTimeRelative') ? Optional.present(json[r'dateTimeRelative'] == null ? null : int.parse('${json[r'dateTimeRelative']}')) : const Optional.absent(), + description: json.containsKey(r'description') ? Optional.present(mapValueOfType(json, r'description')) : const Optional.absent(), + duplicateId: json.containsKey(r'duplicateId') ? Optional.present(mapValueOfType(json, r'duplicateId')) : const Optional.absent(), ids: json[r'ids'] is Iterable ? (json[r'ids'] as Iterable).cast().toList(growable: false) : const [], - isFavorite: mapValueOfType(json, r'isFavorite'), - latitude: num.parse('${json[r'latitude']}'), - longitude: num.parse('${json[r'longitude']}'), - rating: mapValueOfType(json, r'rating'), - timeZone: mapValueOfType(json, r'timeZone'), - visibility: AssetVisibility.fromJson(json[r'visibility']), + isFavorite: json.containsKey(r'isFavorite') ? Optional.present(mapValueOfType(json, r'isFavorite')) : const Optional.absent(), + latitude: json.containsKey(r'latitude') ? Optional.present(json[r'latitude'] == null ? null : num.parse('${json[r'latitude']}')) : const Optional.absent(), + longitude: json.containsKey(r'longitude') ? Optional.present(json[r'longitude'] == null ? null : num.parse('${json[r'longitude']}')) : const Optional.absent(), + rating: json.containsKey(r'rating') ? Optional.present(json[r'rating'] == null ? null : int.parse('${json[r'rating']}')) : const Optional.absent(), + timeZone: json.containsKey(r'timeZone') ? Optional.present(mapValueOfType(json, r'timeZone')) : const Optional.absent(), + visibility: json.containsKey(r'visibility') ? Optional.present(AssetVisibility.fromJson(json[r'visibility'])) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/asset_bulk_upload_check_result.dart b/mobile/openapi/lib/model/asset_bulk_upload_check_result.dart index bf3ee8e244..ed7508b18e 100644 --- a/mobile/openapi/lib/model/asset_bulk_upload_check_result.dart +++ b/mobile/openapi/lib/model/asset_bulk_upload_check_result.dart @@ -14,10 +14,10 @@ class AssetBulkUploadCheckResult { /// Returns a new [AssetBulkUploadCheckResult] instance. AssetBulkUploadCheckResult({ required this.action, - this.assetId, + this.assetId = const Optional.absent(), required this.id, - this.isTrashed, - this.reason, + this.isTrashed = const Optional.absent(), + this.reason = const Optional.absent(), }); AssetUploadAction action; @@ -29,7 +29,7 @@ class AssetBulkUploadCheckResult { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? assetId; + Optional assetId; /// Asset ID String id; @@ -41,7 +41,7 @@ class AssetBulkUploadCheckResult { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? isTrashed; + Optional isTrashed; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -49,7 +49,7 @@ class AssetBulkUploadCheckResult { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - AssetRejectReason? reason; + Optional reason; @override bool operator ==(Object other) => identical(this, other) || other is AssetBulkUploadCheckResult && @@ -74,21 +74,18 @@ class AssetBulkUploadCheckResult { Map toJson() { final json = {}; json[r'action'] = this.action; - if (this.assetId != null) { - json[r'assetId'] = this.assetId; - } else { - // json[r'assetId'] = null; + if (this.assetId.isPresent) { + final value = this.assetId.value; + json[r'assetId'] = value; } json[r'id'] = this.id; - if (this.isTrashed != null) { - json[r'isTrashed'] = this.isTrashed; - } else { - // json[r'isTrashed'] = null; + if (this.isTrashed.isPresent) { + final value = this.isTrashed.value; + json[r'isTrashed'] = value; } - if (this.reason != null) { - json[r'reason'] = this.reason; - } else { - // json[r'reason'] = null; + if (this.reason.isPresent) { + final value = this.reason.value; + json[r'reason'] = value; } return json; } @@ -103,10 +100,10 @@ class AssetBulkUploadCheckResult { return AssetBulkUploadCheckResult( action: AssetUploadAction.fromJson(json[r'action'])!, - assetId: mapValueOfType(json, r'assetId'), + assetId: json.containsKey(r'assetId') ? Optional.present(mapValueOfType(json, r'assetId')) : const Optional.absent(), id: mapValueOfType(json, r'id')!, - isTrashed: mapValueOfType(json, r'isTrashed'), - reason: AssetRejectReason.fromJson(json[r'reason']), + isTrashed: json.containsKey(r'isTrashed') ? Optional.present(mapValueOfType(json, r'isTrashed')) : const Optional.absent(), + reason: json.containsKey(r'reason') ? Optional.present(AssetRejectReason.fromJson(json[r'reason'])) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/asset_copy_dto.dart b/mobile/openapi/lib/model/asset_copy_dto.dart index 2e68c5c113..577ba9ffa6 100644 --- a/mobile/openapi/lib/model/asset_copy_dto.dart +++ b/mobile/openapi/lib/model/asset_copy_dto.dart @@ -13,32 +13,32 @@ part of openapi.api; class AssetCopyDto { /// Returns a new [AssetCopyDto] instance. AssetCopyDto({ - this.albums = true, - this.favorite = true, - this.sharedLinks = true, - this.sidecar = true, + this.albums = const Optional.present(true), + this.favorite = const Optional.present(true), + this.sharedLinks = const Optional.present(true), + this.sidecar = const Optional.present(true), required this.sourceId, - this.stack = true, + this.stack = const Optional.present(true), required this.targetId, }); /// Copy album associations - bool albums; + Optional albums; /// Copy favorite status - bool favorite; + Optional favorite; /// Copy shared links - bool sharedLinks; + Optional sharedLinks; /// Copy sidecar file - bool sidecar; + Optional sidecar; /// Source asset ID String sourceId; /// Copy stack association - bool stack; + Optional stack; /// Target asset ID String targetId; @@ -69,12 +69,27 @@ class AssetCopyDto { Map toJson() { final json = {}; - json[r'albums'] = this.albums; - json[r'favorite'] = this.favorite; - json[r'sharedLinks'] = this.sharedLinks; - json[r'sidecar'] = this.sidecar; + if (this.albums.isPresent) { + final value = this.albums.value; + json[r'albums'] = value; + } + if (this.favorite.isPresent) { + final value = this.favorite.value; + json[r'favorite'] = value; + } + if (this.sharedLinks.isPresent) { + final value = this.sharedLinks.value; + json[r'sharedLinks'] = value; + } + if (this.sidecar.isPresent) { + final value = this.sidecar.value; + json[r'sidecar'] = value; + } json[r'sourceId'] = this.sourceId; - json[r'stack'] = this.stack; + if (this.stack.isPresent) { + final value = this.stack.value; + json[r'stack'] = value; + } json[r'targetId'] = this.targetId; return json; } @@ -88,12 +103,12 @@ class AssetCopyDto { final json = value.cast(); return AssetCopyDto( - albums: mapValueOfType(json, r'albums') ?? true, - favorite: mapValueOfType(json, r'favorite') ?? true, - sharedLinks: mapValueOfType(json, r'sharedLinks') ?? true, - sidecar: mapValueOfType(json, r'sidecar') ?? true, + albums: json.containsKey(r'albums') ? Optional.present(mapValueOfType(json, r'albums')) : const Optional.absent(), + favorite: json.containsKey(r'favorite') ? Optional.present(mapValueOfType(json, r'favorite')) : const Optional.absent(), + sharedLinks: json.containsKey(r'sharedLinks') ? Optional.present(mapValueOfType(json, r'sharedLinks')) : const Optional.absent(), + sidecar: json.containsKey(r'sidecar') ? Optional.present(mapValueOfType(json, r'sidecar')) : const Optional.absent(), sourceId: mapValueOfType(json, r'sourceId')!, - stack: mapValueOfType(json, r'stack') ?? true, + stack: json.containsKey(r'stack') ? Optional.present(mapValueOfType(json, r'stack')) : const Optional.absent(), targetId: mapValueOfType(json, r'targetId')!, ); } diff --git a/mobile/openapi/lib/model/asset_face_response_dto.dart b/mobile/openapi/lib/model/asset_face_response_dto.dart index 21b86dfe4e..aa7b8b65f0 100644 --- a/mobile/openapi/lib/model/asset_face_response_dto.dart +++ b/mobile/openapi/lib/model/asset_face_response_dto.dart @@ -21,7 +21,7 @@ class AssetFaceResponseDto { required this.imageHeight, required this.imageWidth, required this.person, - this.sourceType, + this.sourceType = const Optional.absent(), }); /// Bounding box X1 coordinate @@ -71,7 +71,7 @@ class AssetFaceResponseDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - SourceType? sourceType; + Optional sourceType; @override bool operator ==(Object other) => identical(this, other) || other is AssetFaceResponseDto && @@ -113,12 +113,11 @@ class AssetFaceResponseDto { if (this.person != null) { json[r'person'] = this.person; } else { - // json[r'person'] = null; + json[r'person'] = null; } - if (this.sourceType != null) { - json[r'sourceType'] = this.sourceType; - } else { - // json[r'sourceType'] = null; + if (this.sourceType.isPresent) { + final value = this.sourceType.value; + json[r'sourceType'] = value; } return json; } @@ -140,7 +139,7 @@ class AssetFaceResponseDto { imageHeight: mapValueOfType(json, r'imageHeight')!, imageWidth: mapValueOfType(json, r'imageWidth')!, person: PersonResponseDto.fromJson(json[r'person']), - sourceType: SourceType.fromJson(json[r'sourceType']), + sourceType: json.containsKey(r'sourceType') ? Optional.present(SourceType.fromJson(json[r'sourceType'])) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/asset_face_without_person_response_dto.dart b/mobile/openapi/lib/model/asset_face_without_person_response_dto.dart deleted file mode 100644 index 4a4a2a658e..0000000000 --- a/mobile/openapi/lib/model/asset_face_without_person_response_dto.dart +++ /dev/null @@ -1,189 +0,0 @@ -// -// AUTO-GENERATED FILE, DO NOT MODIFY! -// -// @dart=2.18 - -// ignore_for_file: unused_element, unused_import -// ignore_for_file: always_put_required_named_parameters_first -// ignore_for_file: constant_identifier_names -// ignore_for_file: lines_longer_than_80_chars - -part of openapi.api; - -class AssetFaceWithoutPersonResponseDto { - /// Returns a new [AssetFaceWithoutPersonResponseDto] instance. - AssetFaceWithoutPersonResponseDto({ - required this.boundingBoxX1, - required this.boundingBoxX2, - required this.boundingBoxY1, - required this.boundingBoxY2, - required this.id, - required this.imageHeight, - required this.imageWidth, - this.sourceType, - }); - - /// Bounding box X1 coordinate - /// - /// Minimum value: -9007199254740991 - /// Maximum value: 9007199254740991 - int boundingBoxX1; - - /// Bounding box X2 coordinate - /// - /// Minimum value: -9007199254740991 - /// Maximum value: 9007199254740991 - int boundingBoxX2; - - /// Bounding box Y1 coordinate - /// - /// Minimum value: -9007199254740991 - /// Maximum value: 9007199254740991 - int boundingBoxY1; - - /// Bounding box Y2 coordinate - /// - /// Minimum value: -9007199254740991 - /// Maximum value: 9007199254740991 - int boundingBoxY2; - - /// Face ID - String id; - - /// Image height in pixels - /// - /// Minimum value: 0 - /// Maximum value: 9007199254740991 - int imageHeight; - - /// Image width in pixels - /// - /// Minimum value: 0 - /// Maximum value: 9007199254740991 - int imageWidth; - - /// - /// Please note: This property should have been non-nullable! Since the specification file - /// does not include a default value (using the "default:" property), however, the generated - /// source code must fall back to having a nullable type. - /// Consider adding a "default:" property in the specification file to hide this note. - /// - SourceType? sourceType; - - @override - bool operator ==(Object other) => identical(this, other) || other is AssetFaceWithoutPersonResponseDto && - other.boundingBoxX1 == boundingBoxX1 && - other.boundingBoxX2 == boundingBoxX2 && - other.boundingBoxY1 == boundingBoxY1 && - other.boundingBoxY2 == boundingBoxY2 && - other.id == id && - other.imageHeight == imageHeight && - other.imageWidth == imageWidth && - other.sourceType == sourceType; - - @override - int get hashCode => - // ignore: unnecessary_parenthesis - (boundingBoxX1.hashCode) + - (boundingBoxX2.hashCode) + - (boundingBoxY1.hashCode) + - (boundingBoxY2.hashCode) + - (id.hashCode) + - (imageHeight.hashCode) + - (imageWidth.hashCode) + - (sourceType == null ? 0 : sourceType!.hashCode); - - @override - String toString() => 'AssetFaceWithoutPersonResponseDto[boundingBoxX1=$boundingBoxX1, boundingBoxX2=$boundingBoxX2, boundingBoxY1=$boundingBoxY1, boundingBoxY2=$boundingBoxY2, id=$id, imageHeight=$imageHeight, imageWidth=$imageWidth, sourceType=$sourceType]'; - - Map toJson() { - final json = {}; - json[r'boundingBoxX1'] = this.boundingBoxX1; - json[r'boundingBoxX2'] = this.boundingBoxX2; - json[r'boundingBoxY1'] = this.boundingBoxY1; - json[r'boundingBoxY2'] = this.boundingBoxY2; - json[r'id'] = this.id; - json[r'imageHeight'] = this.imageHeight; - json[r'imageWidth'] = this.imageWidth; - if (this.sourceType != null) { - json[r'sourceType'] = this.sourceType; - } else { - // json[r'sourceType'] = null; - } - return json; - } - - /// Returns a new [AssetFaceWithoutPersonResponseDto] instance and imports its values from - /// [value] if it's a [Map], null otherwise. - // ignore: prefer_constructors_over_static_methods - static AssetFaceWithoutPersonResponseDto? fromJson(dynamic value) { - upgradeDto(value, "AssetFaceWithoutPersonResponseDto"); - if (value is Map) { - final json = value.cast(); - - return AssetFaceWithoutPersonResponseDto( - boundingBoxX1: mapValueOfType(json, r'boundingBoxX1')!, - boundingBoxX2: mapValueOfType(json, r'boundingBoxX2')!, - boundingBoxY1: mapValueOfType(json, r'boundingBoxY1')!, - boundingBoxY2: mapValueOfType(json, r'boundingBoxY2')!, - id: mapValueOfType(json, r'id')!, - imageHeight: mapValueOfType(json, r'imageHeight')!, - imageWidth: mapValueOfType(json, r'imageWidth')!, - sourceType: SourceType.fromJson(json[r'sourceType']), - ); - } - return null; - } - - static List listFromJson(dynamic json, {bool growable = false,}) { - final result = []; - if (json is List && json.isNotEmpty) { - for (final row in json) { - final value = AssetFaceWithoutPersonResponseDto.fromJson(row); - if (value != null) { - result.add(value); - } - } - } - return result.toList(growable: growable); - } - - static Map mapFromJson(dynamic json) { - final map = {}; - if (json is Map && json.isNotEmpty) { - json = json.cast(); // ignore: parameter_assignments - for (final entry in json.entries) { - final value = AssetFaceWithoutPersonResponseDto.fromJson(entry.value); - if (value != null) { - map[entry.key] = value; - } - } - } - return map; - } - - // maps a json object with a list of AssetFaceWithoutPersonResponseDto-objects as value to a dart map - static Map> mapListFromJson(dynamic json, {bool growable = false,}) { - final map = >{}; - if (json is Map && json.isNotEmpty) { - // ignore: parameter_assignments - json = json.cast(); - for (final entry in json.entries) { - map[entry.key] = AssetFaceWithoutPersonResponseDto.listFromJson(entry.value, growable: growable,); - } - } - return map; - } - - /// The list of required keys that must be present in a JSON. - static const requiredKeys = { - 'boundingBoxX1', - 'boundingBoxX2', - 'boundingBoxY1', - 'boundingBoxY2', - 'id', - 'imageHeight', - 'imageWidth', - }; -} - diff --git a/mobile/openapi/lib/model/asset_ids_response_dto.dart b/mobile/openapi/lib/model/asset_ids_response_dto.dart index cafe1b21b9..6d8076952e 100644 --- a/mobile/openapi/lib/model/asset_ids_response_dto.dart +++ b/mobile/openapi/lib/model/asset_ids_response_dto.dart @@ -14,7 +14,7 @@ class AssetIdsResponseDto { /// Returns a new [AssetIdsResponseDto] instance. AssetIdsResponseDto({ required this.assetId, - this.error, + this.error = const Optional.absent(), required this.success, }); @@ -27,7 +27,7 @@ class AssetIdsResponseDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - AssetIdErrorReason? error; + Optional error; /// Whether operation succeeded bool success; @@ -51,10 +51,9 @@ class AssetIdsResponseDto { Map toJson() { final json = {}; json[r'assetId'] = this.assetId; - if (this.error != null) { - json[r'error'] = this.error; - } else { - // json[r'error'] = null; + if (this.error.isPresent) { + final value = this.error.value; + json[r'error'] = value; } json[r'success'] = this.success; return json; @@ -70,7 +69,7 @@ class AssetIdsResponseDto { return AssetIdsResponseDto( assetId: mapValueOfType(json, r'assetId')!, - error: AssetIdErrorReason.fromJson(json[r'error']), + error: json.containsKey(r'error') ? Optional.present(AssetIdErrorReason.fromJson(json[r'error'])) : const Optional.absent(), success: mapValueOfType(json, r'success')!, ); } diff --git a/mobile/openapi/lib/model/asset_ocr_response_dto.dart b/mobile/openapi/lib/model/asset_ocr_response_dto.dart index c7937c6eb2..23c51f054c 100644 --- a/mobile/openapi/lib/model/asset_ocr_response_dto.dart +++ b/mobile/openapi/lib/model/asset_ocr_response_dto.dart @@ -129,18 +129,18 @@ class AssetOcrResponseDto { return AssetOcrResponseDto( assetId: mapValueOfType(json, r'assetId')!, - boxScore: (mapValueOfType(json, r'boxScore')!).toDouble(), + boxScore: mapValueOfType(json, r'boxScore')!, id: mapValueOfType(json, r'id')!, text: mapValueOfType(json, r'text')!, - textScore: (mapValueOfType(json, r'textScore')!).toDouble(), - x1: (mapValueOfType(json, r'x1')!).toDouble(), - x2: (mapValueOfType(json, r'x2')!).toDouble(), - x3: (mapValueOfType(json, r'x3')!).toDouble(), - x4: (mapValueOfType(json, r'x4')!).toDouble(), - y1: (mapValueOfType(json, r'y1')!).toDouble(), - y2: (mapValueOfType(json, r'y2')!).toDouble(), - y3: (mapValueOfType(json, r'y3')!).toDouble(), - y4: (mapValueOfType(json, r'y4')!).toDouble(), + textScore: mapValueOfType(json, r'textScore')!, + x1: mapValueOfType(json, r'x1')!, + x2: mapValueOfType(json, r'x2')!, + x3: mapValueOfType(json, r'x3')!, + x4: mapValueOfType(json, r'x4')!, + y1: mapValueOfType(json, r'y1')!, + y2: mapValueOfType(json, r'y2')!, + y3: mapValueOfType(json, r'y3')!, + y4: mapValueOfType(json, r'y4')!, ); } return null; diff --git a/mobile/openapi/lib/model/asset_order_by.dart b/mobile/openapi/lib/model/asset_order_by.dart new file mode 100644 index 0000000000..2edba961d9 --- /dev/null +++ b/mobile/openapi/lib/model/asset_order_by.dart @@ -0,0 +1,85 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +/// Asset sorting property +class AssetOrderBy { + /// Instantiate a new enum with the provided [value]. + const AssetOrderBy._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const takenAt = AssetOrderBy._(r'takenAt'); + static const createdAt = AssetOrderBy._(r'createdAt'); + + /// List of all possible values in this [enum][AssetOrderBy]. + static const values = [ + takenAt, + createdAt, + ]; + + static AssetOrderBy? fromJson(dynamic value) => AssetOrderByTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AssetOrderBy.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [AssetOrderBy] to String, +/// and [decode] dynamic data back to [AssetOrderBy]. +class AssetOrderByTypeTransformer { + factory AssetOrderByTypeTransformer() => _instance ??= const AssetOrderByTypeTransformer._(); + + const AssetOrderByTypeTransformer._(); + + String encode(AssetOrderBy data) => data.value; + + /// Decodes a [dynamic value][data] to a AssetOrderBy. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + AssetOrderBy? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'takenAt': return AssetOrderBy.takenAt; + case r'createdAt': return AssetOrderBy.createdAt; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [AssetOrderByTypeTransformer] instance. + static AssetOrderByTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/asset_response_dto.dart b/mobile/openapi/lib/model/asset_response_dto.dart index 7284c44580..3c09de3f15 100644 --- a/mobile/openapi/lib/model/asset_response_dto.dart +++ b/mobile/openapi/lib/model/asset_response_dto.dart @@ -15,9 +15,9 @@ class AssetResponseDto { AssetResponseDto({ required this.checksum, required this.createdAt, - this.duplicateId, + this.duplicateId = const Optional.absent(), required this.duration, - this.exifInfo, + this.exifInfo = const Optional.absent(), required this.fileCreatedAt, required this.fileModifiedAt, required this.hasMetadata, @@ -28,21 +28,20 @@ class AssetResponseDto { required this.isFavorite, required this.isOffline, required this.isTrashed, - this.libraryId, - this.livePhotoVideoId, + this.libraryId = const Optional.absent(), + this.livePhotoVideoId = const Optional.absent(), required this.localDateTime, required this.originalFileName, - this.originalMimeType, + this.originalMimeType = const Optional.absent(), required this.originalPath, - this.owner, + this.owner = const Optional.absent(), required this.ownerId, - this.people = const [], - this.resized, - this.stack, - this.tags = const [], + this.people = const Optional.present(const []), + this.resized = const Optional.absent(), + this.stack = const Optional.absent(), + this.tags = const Optional.present(const []), required this.thumbhash, required this.type, - this.unassignedFaces = const [], required this.updatedAt, required this.visibility, required this.width, @@ -55,7 +54,7 @@ class AssetResponseDto { DateTime createdAt; /// Duplicate group ID - String? duplicateId; + Optional duplicateId; /// Video/gif duration in milliseconds (null for static images) /// @@ -69,7 +68,7 @@ class AssetResponseDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - ExifResponseDto? exifInfo; + Optional exifInfo; /// The actual UTC timestamp when the file was created/captured, preserving timezone information. This is the authoritative timestamp for chronological sorting within timeline groups. Combined with timezone data, this can be used to determine the exact moment the photo was taken. DateTime fileCreatedAt; @@ -105,10 +104,10 @@ class AssetResponseDto { bool isTrashed; /// Library ID - String? libraryId; + Optional libraryId; /// Live photo video ID - String? livePhotoVideoId; + Optional livePhotoVideoId; /// The local date and time when the photo/video was taken, derived from EXIF metadata. This represents the photographer's local time regardless of timezone, stored as a timezone-agnostic timestamp. Used for timeline grouping by \"local\" days and months. DateTime localDateTime; @@ -123,7 +122,7 @@ class AssetResponseDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? originalMimeType; + Optional originalMimeType; /// Original file path String originalPath; @@ -134,12 +133,12 @@ class AssetResponseDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - UserResponseDto? owner; + Optional owner; /// Owner user ID String ownerId; - List people; + Optional?> people; /// Is resized /// @@ -148,19 +147,17 @@ class AssetResponseDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? resized; + Optional resized; - AssetStackResponseDto? stack; + Optional stack; - List tags; + Optional?> tags; /// Thumbhash for thumbnail generation (base64) also used as the c query param for thumbnail cache busting. String? thumbhash; AssetTypeEnum type; - List unassignedFaces; - /// The UTC timestamp when the asset record was last updated in the database. This is automatically maintained by the database and reflects when any field in the asset was last modified. DateTime updatedAt; @@ -203,7 +200,6 @@ class AssetResponseDto { _deepEquality.equals(other.tags, tags) && other.thumbhash == thumbhash && other.type == type && - _deepEquality.equals(other.unassignedFaces, unassignedFaces) && other.updatedAt == updatedAt && other.visibility == visibility && other.width == width; @@ -240,32 +236,29 @@ class AssetResponseDto { (tags.hashCode) + (thumbhash == null ? 0 : thumbhash!.hashCode) + (type.hashCode) + - (unassignedFaces.hashCode) + (updatedAt.hashCode) + (visibility.hashCode) + (width == null ? 0 : width!.hashCode); @override - String toString() => 'AssetResponseDto[checksum=$checksum, createdAt=$createdAt, duplicateId=$duplicateId, duration=$duration, exifInfo=$exifInfo, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, hasMetadata=$hasMetadata, height=$height, id=$id, isArchived=$isArchived, isEdited=$isEdited, isFavorite=$isFavorite, isOffline=$isOffline, isTrashed=$isTrashed, libraryId=$libraryId, livePhotoVideoId=$livePhotoVideoId, localDateTime=$localDateTime, originalFileName=$originalFileName, originalMimeType=$originalMimeType, originalPath=$originalPath, owner=$owner, ownerId=$ownerId, people=$people, resized=$resized, stack=$stack, tags=$tags, thumbhash=$thumbhash, type=$type, unassignedFaces=$unassignedFaces, updatedAt=$updatedAt, visibility=$visibility, width=$width]'; + String toString() => 'AssetResponseDto[checksum=$checksum, createdAt=$createdAt, duplicateId=$duplicateId, duration=$duration, exifInfo=$exifInfo, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, hasMetadata=$hasMetadata, height=$height, id=$id, isArchived=$isArchived, isEdited=$isEdited, isFavorite=$isFavorite, isOffline=$isOffline, isTrashed=$isTrashed, libraryId=$libraryId, livePhotoVideoId=$livePhotoVideoId, localDateTime=$localDateTime, originalFileName=$originalFileName, originalMimeType=$originalMimeType, originalPath=$originalPath, owner=$owner, ownerId=$ownerId, people=$people, resized=$resized, stack=$stack, tags=$tags, thumbhash=$thumbhash, type=$type, updatedAt=$updatedAt, visibility=$visibility, width=$width]'; Map toJson() { final json = {}; json[r'checksum'] = this.checksum; json[r'createdAt'] = this.createdAt.toUtc().toIso8601String(); - if (this.duplicateId != null) { - json[r'duplicateId'] = this.duplicateId; - } else { - // json[r'duplicateId'] = null; + if (this.duplicateId.isPresent) { + final value = this.duplicateId.value; + json[r'duplicateId'] = value; } if (this.duration != null) { json[r'duration'] = this.duration; } else { - // json[r'duration'] = null; + json[r'duration'] = null; } - if (this.exifInfo != null) { - json[r'exifInfo'] = this.exifInfo; - } else { - // json[r'exifInfo'] = null; + if (this.exifInfo.isPresent) { + final value = this.exifInfo.value; + json[r'exifInfo'] = value; } json[r'fileCreatedAt'] = this.fileCreatedAt.toUtc().toIso8601String(); json[r'fileModifiedAt'] = this.fileModifiedAt.toUtc().toIso8601String(); @@ -273,7 +266,7 @@ class AssetResponseDto { if (this.height != null) { json[r'height'] = this.height; } else { - // json[r'height'] = null; + json[r'height'] = null; } json[r'id'] = this.id; json[r'isArchived'] = this.isArchived; @@ -281,55 +274,54 @@ class AssetResponseDto { json[r'isFavorite'] = this.isFavorite; json[r'isOffline'] = this.isOffline; json[r'isTrashed'] = this.isTrashed; - if (this.libraryId != null) { - json[r'libraryId'] = this.libraryId; - } else { - // json[r'libraryId'] = null; + if (this.libraryId.isPresent) { + final value = this.libraryId.value; + json[r'libraryId'] = value; } - if (this.livePhotoVideoId != null) { - json[r'livePhotoVideoId'] = this.livePhotoVideoId; - } else { - // json[r'livePhotoVideoId'] = null; + if (this.livePhotoVideoId.isPresent) { + final value = this.livePhotoVideoId.value; + json[r'livePhotoVideoId'] = value; } json[r'localDateTime'] = this.localDateTime.toUtc().toIso8601String(); json[r'originalFileName'] = this.originalFileName; - if (this.originalMimeType != null) { - json[r'originalMimeType'] = this.originalMimeType; - } else { - // json[r'originalMimeType'] = null; + if (this.originalMimeType.isPresent) { + final value = this.originalMimeType.value; + json[r'originalMimeType'] = value; } json[r'originalPath'] = this.originalPath; - if (this.owner != null) { - json[r'owner'] = this.owner; - } else { - // json[r'owner'] = null; + if (this.owner.isPresent) { + final value = this.owner.value; + json[r'owner'] = value; } json[r'ownerId'] = this.ownerId; - json[r'people'] = this.people; - if (this.resized != null) { - json[r'resized'] = this.resized; - } else { - // json[r'resized'] = null; + if (this.people.isPresent) { + final value = this.people.value; + json[r'people'] = value; } - if (this.stack != null) { - json[r'stack'] = this.stack; - } else { - // json[r'stack'] = null; + if (this.resized.isPresent) { + final value = this.resized.value; + json[r'resized'] = value; + } + if (this.stack.isPresent) { + final value = this.stack.value; + json[r'stack'] = value; + } + if (this.tags.isPresent) { + final value = this.tags.value; + json[r'tags'] = value; } - json[r'tags'] = this.tags; if (this.thumbhash != null) { json[r'thumbhash'] = this.thumbhash; } else { - // json[r'thumbhash'] = null; + json[r'thumbhash'] = null; } json[r'type'] = this.type; - json[r'unassignedFaces'] = this.unassignedFaces; json[r'updatedAt'] = this.updatedAt.toUtc().toIso8601String(); json[r'visibility'] = this.visibility; if (this.width != null) { json[r'width'] = this.width; } else { - // json[r'width'] = null; + json[r'width'] = null; } return json; } @@ -345,9 +337,9 @@ class AssetResponseDto { return AssetResponseDto( checksum: mapValueOfType(json, r'checksum')!, createdAt: mapDateTime(json, r'createdAt', r'')!, - duplicateId: mapValueOfType(json, r'duplicateId'), + duplicateId: json.containsKey(r'duplicateId') ? Optional.present(mapValueOfType(json, r'duplicateId')) : const Optional.absent(), duration: mapValueOfType(json, r'duration'), - exifInfo: ExifResponseDto.fromJson(json[r'exifInfo']), + exifInfo: json.containsKey(r'exifInfo') ? Optional.present(ExifResponseDto.fromJson(json[r'exifInfo'])) : const Optional.absent(), fileCreatedAt: mapDateTime(json, r'fileCreatedAt', r'')!, fileModifiedAt: mapDateTime(json, r'fileModifiedAt', r'')!, hasMetadata: mapValueOfType(json, r'hasMetadata')!, @@ -358,21 +350,20 @@ class AssetResponseDto { isFavorite: mapValueOfType(json, r'isFavorite')!, isOffline: mapValueOfType(json, r'isOffline')!, isTrashed: mapValueOfType(json, r'isTrashed')!, - libraryId: mapValueOfType(json, r'libraryId'), - livePhotoVideoId: mapValueOfType(json, r'livePhotoVideoId'), + libraryId: json.containsKey(r'libraryId') ? Optional.present(mapValueOfType(json, r'libraryId')) : const Optional.absent(), + livePhotoVideoId: json.containsKey(r'livePhotoVideoId') ? Optional.present(mapValueOfType(json, r'livePhotoVideoId')) : const Optional.absent(), localDateTime: mapDateTime(json, r'localDateTime', r'')!, originalFileName: mapValueOfType(json, r'originalFileName')!, - originalMimeType: mapValueOfType(json, r'originalMimeType'), + originalMimeType: json.containsKey(r'originalMimeType') ? Optional.present(mapValueOfType(json, r'originalMimeType')) : const Optional.absent(), originalPath: mapValueOfType(json, r'originalPath')!, - owner: UserResponseDto.fromJson(json[r'owner']), + owner: json.containsKey(r'owner') ? Optional.present(UserResponseDto.fromJson(json[r'owner'])) : const Optional.absent(), ownerId: mapValueOfType(json, r'ownerId')!, - people: PersonWithFacesResponseDto.listFromJson(json[r'people']), - resized: mapValueOfType(json, r'resized'), - stack: AssetStackResponseDto.fromJson(json[r'stack']), - tags: TagResponseDto.listFromJson(json[r'tags']), + people: json.containsKey(r'people') ? Optional.present(PersonResponseDto.listFromJson(json[r'people'])) : const Optional.absent(), + resized: json.containsKey(r'resized') ? Optional.present(mapValueOfType(json, r'resized')) : const Optional.absent(), + stack: json.containsKey(r'stack') ? Optional.present(AssetStackResponseDto.fromJson(json[r'stack'])) : const Optional.absent(), + tags: json.containsKey(r'tags') ? Optional.present(TagResponseDto.listFromJson(json[r'tags'])) : const Optional.absent(), thumbhash: mapValueOfType(json, r'thumbhash'), type: AssetTypeEnum.fromJson(json[r'type'])!, - unassignedFaces: AssetFaceWithoutPersonResponseDto.listFromJson(json[r'unassignedFaces']), updatedAt: mapDateTime(json, r'updatedAt', r'')!, visibility: AssetVisibility.fromJson(json[r'visibility'])!, width: mapValueOfType(json, r'width'), diff --git a/mobile/openapi/lib/model/audio_codec.dart b/mobile/openapi/lib/model/audio_codec.dart index be1ff0dcb9..d1c10e08a1 100644 --- a/mobile/openapi/lib/model/audio_codec.dart +++ b/mobile/openapi/lib/model/audio_codec.dart @@ -25,7 +25,6 @@ class AudioCodec { static const mp3 = AudioCodec._(r'mp3'); static const aac = AudioCodec._(r'aac'); - static const libopus = AudioCodec._(r'libopus'); static const opus = AudioCodec._(r'opus'); static const pcmS16le = AudioCodec._(r'pcm_s16le'); @@ -33,7 +32,6 @@ class AudioCodec { static const values = [ mp3, aac, - libopus, opus, pcmS16le, ]; @@ -76,7 +74,6 @@ class AudioCodecTypeTransformer { switch (data) { case r'mp3': return AudioCodec.mp3; case r'aac': return AudioCodec.aac; - case r'libopus': return AudioCodec.libopus; case r'opus': return AudioCodec.opus; case r'pcm_s16le': return AudioCodec.pcmS16le; default: diff --git a/mobile/openapi/lib/model/auth_status_response_dto.dart b/mobile/openapi/lib/model/auth_status_response_dto.dart index 23b9d40525..f0dc61215e 100644 --- a/mobile/openapi/lib/model/auth_status_response_dto.dart +++ b/mobile/openapi/lib/model/auth_status_response_dto.dart @@ -13,11 +13,11 @@ part of openapi.api; class AuthStatusResponseDto { /// Returns a new [AuthStatusResponseDto] instance. AuthStatusResponseDto({ - this.expiresAt, + this.expiresAt = const Optional.absent(), required this.isElevated, required this.password, required this.pinCode, - this.pinExpiresAt, + this.pinExpiresAt = const Optional.absent(), }); /// Session expiration date @@ -27,7 +27,7 @@ class AuthStatusResponseDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? expiresAt; + Optional expiresAt; /// Is elevated session bool isElevated; @@ -45,7 +45,7 @@ class AuthStatusResponseDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? pinExpiresAt; + Optional pinExpiresAt; @override bool operator ==(Object other) => identical(this, other) || other is AuthStatusResponseDto && @@ -69,18 +69,16 @@ class AuthStatusResponseDto { Map toJson() { final json = {}; - if (this.expiresAt != null) { - json[r'expiresAt'] = this.expiresAt; - } else { - // json[r'expiresAt'] = null; + if (this.expiresAt.isPresent) { + final value = this.expiresAt.value; + json[r'expiresAt'] = value; } json[r'isElevated'] = this.isElevated; json[r'password'] = this.password; json[r'pinCode'] = this.pinCode; - if (this.pinExpiresAt != null) { - json[r'pinExpiresAt'] = this.pinExpiresAt; - } else { - // json[r'pinExpiresAt'] = null; + if (this.pinExpiresAt.isPresent) { + final value = this.pinExpiresAt.value; + json[r'pinExpiresAt'] = value; } return json; } @@ -94,11 +92,11 @@ class AuthStatusResponseDto { final json = value.cast(); return AuthStatusResponseDto( - expiresAt: mapValueOfType(json, r'expiresAt'), + expiresAt: json.containsKey(r'expiresAt') ? Optional.present(mapValueOfType(json, r'expiresAt')) : const Optional.absent(), isElevated: mapValueOfType(json, r'isElevated')!, password: mapValueOfType(json, r'password')!, pinCode: mapValueOfType(json, r'pinCode')!, - pinExpiresAt: mapValueOfType(json, r'pinExpiresAt'), + pinExpiresAt: json.containsKey(r'pinExpiresAt') ? Optional.present(mapValueOfType(json, r'pinExpiresAt')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/avatar_update.dart b/mobile/openapi/lib/model/avatar_update.dart index 875eb138a8..1075f0df46 100644 --- a/mobile/openapi/lib/model/avatar_update.dart +++ b/mobile/openapi/lib/model/avatar_update.dart @@ -13,7 +13,7 @@ part of openapi.api; class AvatarUpdate { /// Returns a new [AvatarUpdate] instance. AvatarUpdate({ - this.color, + this.color = const Optional.absent(), }); /// @@ -22,7 +22,7 @@ class AvatarUpdate { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - UserAvatarColor? color; + Optional color; @override bool operator ==(Object other) => identical(this, other) || other is AvatarUpdate && @@ -38,10 +38,9 @@ class AvatarUpdate { Map toJson() { final json = {}; - if (this.color != null) { - json[r'color'] = this.color; - } else { - // json[r'color'] = null; + if (this.color.isPresent) { + final value = this.color.value; + json[r'color'] = value; } return json; } @@ -55,7 +54,7 @@ class AvatarUpdate { final json = value.cast(); return AvatarUpdate( - color: UserAvatarColor.fromJson(json[r'color']), + color: json.containsKey(r'color') ? Optional.present(UserAvatarColor.fromJson(json[r'color'])) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/bulk_id_response_dto.dart b/mobile/openapi/lib/model/bulk_id_response_dto.dart index bb3f1d8856..301400fa5e 100644 --- a/mobile/openapi/lib/model/bulk_id_response_dto.dart +++ b/mobile/openapi/lib/model/bulk_id_response_dto.dart @@ -13,8 +13,8 @@ part of openapi.api; class BulkIdResponseDto { /// Returns a new [BulkIdResponseDto] instance. BulkIdResponseDto({ - this.error, - this.errorMessage, + this.error = const Optional.absent(), + this.errorMessage = const Optional.absent(), required this.id, required this.success, }); @@ -25,7 +25,7 @@ class BulkIdResponseDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - BulkIdErrorReason? error; + Optional error; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -33,7 +33,7 @@ class BulkIdResponseDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? errorMessage; + Optional errorMessage; /// ID String id; @@ -61,15 +61,13 @@ class BulkIdResponseDto { Map toJson() { final json = {}; - if (this.error != null) { - json[r'error'] = this.error; - } else { - // json[r'error'] = null; + if (this.error.isPresent) { + final value = this.error.value; + json[r'error'] = value; } - if (this.errorMessage != null) { - json[r'errorMessage'] = this.errorMessage; - } else { - // json[r'errorMessage'] = null; + if (this.errorMessage.isPresent) { + final value = this.errorMessage.value; + json[r'errorMessage'] = value; } json[r'id'] = this.id; json[r'success'] = this.success; @@ -85,8 +83,8 @@ class BulkIdResponseDto { final json = value.cast(); return BulkIdResponseDto( - error: BulkIdErrorReason.fromJson(json[r'error']), - errorMessage: mapValueOfType(json, r'errorMessage'), + error: json.containsKey(r'error') ? Optional.present(BulkIdErrorReason.fromJson(json[r'error'])) : const Optional.absent(), + errorMessage: json.containsKey(r'errorMessage') ? Optional.present(mapValueOfType(json, r'errorMessage')) : const Optional.absent(), id: mapValueOfType(json, r'id')!, success: mapValueOfType(json, r'success')!, ); diff --git a/mobile/openapi/lib/model/cast_update.dart b/mobile/openapi/lib/model/cast_update.dart index 8dbf80f171..f9eb5be382 100644 --- a/mobile/openapi/lib/model/cast_update.dart +++ b/mobile/openapi/lib/model/cast_update.dart @@ -13,7 +13,7 @@ part of openapi.api; class CastUpdate { /// Returns a new [CastUpdate] instance. CastUpdate({ - this.gCastEnabled, + this.gCastEnabled = const Optional.absent(), }); /// Whether Google Cast is enabled @@ -23,7 +23,7 @@ class CastUpdate { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? gCastEnabled; + Optional gCastEnabled; @override bool operator ==(Object other) => identical(this, other) || other is CastUpdate && @@ -39,10 +39,9 @@ class CastUpdate { Map toJson() { final json = {}; - if (this.gCastEnabled != null) { - json[r'gCastEnabled'] = this.gCastEnabled; - } else { - // json[r'gCastEnabled'] = null; + if (this.gCastEnabled.isPresent) { + final value = this.gCastEnabled.value; + json[r'gCastEnabled'] = value; } return json; } @@ -56,7 +55,7 @@ class CastUpdate { final json = value.cast(); return CastUpdate( - gCastEnabled: mapValueOfType(json, r'gCastEnabled'), + gCastEnabled: json.containsKey(r'gCastEnabled') ? Optional.present(mapValueOfType(json, r'gCastEnabled')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/change_password_dto.dart b/mobile/openapi/lib/model/change_password_dto.dart index 3dd6e437da..369e960dac 100644 --- a/mobile/openapi/lib/model/change_password_dto.dart +++ b/mobile/openapi/lib/model/change_password_dto.dart @@ -13,13 +13,13 @@ part of openapi.api; class ChangePasswordDto { /// Returns a new [ChangePasswordDto] instance. ChangePasswordDto({ - this.invalidateSessions = false, + this.invalidateSessions = const Optional.present(false), required this.newPassword, required this.password, }); /// Invalidate all other sessions - bool invalidateSessions; + Optional invalidateSessions; /// New password (min 8 characters) String newPassword; @@ -45,7 +45,10 @@ class ChangePasswordDto { Map toJson() { final json = {}; - json[r'invalidateSessions'] = this.invalidateSessions; + if (this.invalidateSessions.isPresent) { + final value = this.invalidateSessions.value; + json[r'invalidateSessions'] = value; + } json[r'newPassword'] = this.newPassword; json[r'password'] = this.password; return json; @@ -60,7 +63,7 @@ class ChangePasswordDto { final json = value.cast(); return ChangePasswordDto( - invalidateSessions: mapValueOfType(json, r'invalidateSessions') ?? false, + invalidateSessions: json.containsKey(r'invalidateSessions') ? Optional.present(mapValueOfType(json, r'invalidateSessions')) : const Optional.absent(), newPassword: mapValueOfType(json, r'newPassword')!, password: mapValueOfType(json, r'password')!, ); diff --git a/mobile/openapi/lib/model/create_album_dto.dart b/mobile/openapi/lib/model/create_album_dto.dart index 183a41c772..a028146964 100644 --- a/mobile/openapi/lib/model/create_album_dto.dart +++ b/mobile/openapi/lib/model/create_album_dto.dart @@ -14,19 +14,19 @@ class CreateAlbumDto { /// Returns a new [CreateAlbumDto] instance. CreateAlbumDto({ required this.albumName, - this.albumUsers = const [], - this.assetIds = const [], - this.description, + this.albumUsers = const Optional.present(const []), + this.assetIds = const Optional.present(const []), + this.description = const Optional.absent(), }); /// Album name String albumName; /// Album users - List albumUsers; + Optional?> albumUsers; /// Initial asset IDs - List assetIds; + Optional?> assetIds; /// Album description /// @@ -35,7 +35,7 @@ class CreateAlbumDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? description; + Optional description; @override bool operator ==(Object other) => identical(this, other) || other is CreateAlbumDto && @@ -58,12 +58,17 @@ class CreateAlbumDto { Map toJson() { final json = {}; json[r'albumName'] = this.albumName; - json[r'albumUsers'] = this.albumUsers; - json[r'assetIds'] = this.assetIds; - if (this.description != null) { - json[r'description'] = this.description; - } else { - // json[r'description'] = null; + if (this.albumUsers.isPresent) { + final value = this.albumUsers.value; + json[r'albumUsers'] = value; + } + if (this.assetIds.isPresent) { + final value = this.assetIds.value; + json[r'assetIds'] = value; + } + if (this.description.isPresent) { + final value = this.description.value; + json[r'description'] = value; } return json; } @@ -78,11 +83,11 @@ class CreateAlbumDto { return CreateAlbumDto( albumName: mapValueOfType(json, r'albumName')!, - albumUsers: AlbumUserCreateDto.listFromJson(json[r'albumUsers']), - assetIds: json[r'assetIds'] is Iterable + albumUsers: json.containsKey(r'albumUsers') ? Optional.present(AlbumUserCreateDto.listFromJson(json[r'albumUsers'])) : const Optional.absent(), + assetIds: json.containsKey(r'assetIds') ? Optional.present(json[r'assetIds'] is Iterable ? (json[r'assetIds'] as Iterable).cast().toList(growable: false) - : const [], - description: mapValueOfType(json, r'description'), + : const []) : const Optional.absent(), + description: json.containsKey(r'description') ? Optional.present(mapValueOfType(json, r'description')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/create_library_dto.dart b/mobile/openapi/lib/model/create_library_dto.dart index ba12c62d76..61eb9867b8 100644 --- a/mobile/openapi/lib/model/create_library_dto.dart +++ b/mobile/openapi/lib/model/create_library_dto.dart @@ -13,17 +13,17 @@ part of openapi.api; class CreateLibraryDto { /// Returns a new [CreateLibraryDto] instance. CreateLibraryDto({ - this.exclusionPatterns = const [], - this.importPaths = const [], - this.name, + this.exclusionPatterns = const Optional.present(const []), + this.importPaths = const Optional.present(const []), + this.name = const Optional.absent(), required this.ownerId, }); /// Exclusion patterns (max 128) - List exclusionPatterns; + Optional?> exclusionPatterns; /// Import paths (max 128) - List importPaths; + Optional?> importPaths; /// Library name /// @@ -32,7 +32,7 @@ class CreateLibraryDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? name; + Optional name; /// Owner user ID String ownerId; @@ -57,12 +57,17 @@ class CreateLibraryDto { Map toJson() { final json = {}; - json[r'exclusionPatterns'] = this.exclusionPatterns; - json[r'importPaths'] = this.importPaths; - if (this.name != null) { - json[r'name'] = this.name; - } else { - // json[r'name'] = null; + if (this.exclusionPatterns.isPresent) { + final value = this.exclusionPatterns.value; + json[r'exclusionPatterns'] = value; + } + if (this.importPaths.isPresent) { + final value = this.importPaths.value; + json[r'importPaths'] = value; + } + if (this.name.isPresent) { + final value = this.name.value; + json[r'name'] = value; } json[r'ownerId'] = this.ownerId; return json; @@ -77,13 +82,13 @@ class CreateLibraryDto { final json = value.cast(); return CreateLibraryDto( - exclusionPatterns: json[r'exclusionPatterns'] is Iterable + exclusionPatterns: json.containsKey(r'exclusionPatterns') ? Optional.present(json[r'exclusionPatterns'] is Iterable ? (json[r'exclusionPatterns'] as Iterable).cast().toList(growable: false) - : const [], - importPaths: json[r'importPaths'] is Iterable + : const []) : const Optional.absent(), + importPaths: json.containsKey(r'importPaths') ? Optional.present(json[r'importPaths'] is Iterable ? (json[r'importPaths'] as Iterable).cast().toList(growable: false) - : const [], - name: mapValueOfType(json, r'name'), + : const []) : const Optional.absent(), + name: json.containsKey(r'name') ? Optional.present(mapValueOfType(json, r'name')) : const Optional.absent(), ownerId: mapValueOfType(json, r'ownerId')!, ); } diff --git a/mobile/openapi/lib/model/download_archive_dto.dart b/mobile/openapi/lib/model/download_archive_dto.dart index 20e8527f18..f89ac8c867 100644 --- a/mobile/openapi/lib/model/download_archive_dto.dart +++ b/mobile/openapi/lib/model/download_archive_dto.dart @@ -14,7 +14,7 @@ class DownloadArchiveDto { /// Returns a new [DownloadArchiveDto] instance. DownloadArchiveDto({ this.assetIds = const [], - this.edited, + this.edited = const Optional.absent(), }); /// Asset IDs @@ -27,7 +27,7 @@ class DownloadArchiveDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? edited; + Optional edited; @override bool operator ==(Object other) => identical(this, other) || other is DownloadArchiveDto && @@ -46,10 +46,9 @@ class DownloadArchiveDto { Map toJson() { final json = {}; json[r'assetIds'] = this.assetIds; - if (this.edited != null) { - json[r'edited'] = this.edited; - } else { - // json[r'edited'] = null; + if (this.edited.isPresent) { + final value = this.edited.value; + json[r'edited'] = value; } return json; } @@ -66,7 +65,7 @@ class DownloadArchiveDto { assetIds: json[r'assetIds'] is Iterable ? (json[r'assetIds'] as Iterable).cast().toList(growable: false) : const [], - edited: mapValueOfType(json, r'edited'), + edited: json.containsKey(r'edited') ? Optional.present(mapValueOfType(json, r'edited')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/download_info_dto.dart b/mobile/openapi/lib/model/download_info_dto.dart index 8a0cebd945..47e09de05a 100644 --- a/mobile/openapi/lib/model/download_info_dto.dart +++ b/mobile/openapi/lib/model/download_info_dto.dart @@ -13,10 +13,10 @@ part of openapi.api; class DownloadInfoDto { /// Returns a new [DownloadInfoDto] instance. DownloadInfoDto({ - this.albumId, - this.archiveSize, - this.assetIds = const [], - this.userId, + this.albumId = const Optional.absent(), + this.archiveSize = const Optional.absent(), + this.assetIds = const Optional.present(const []), + this.userId = const Optional.absent(), }); /// Album ID to download @@ -26,7 +26,7 @@ class DownloadInfoDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? albumId; + Optional albumId; /// Archive size limit in bytes /// @@ -38,10 +38,10 @@ class DownloadInfoDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - int? archiveSize; + Optional archiveSize; /// Asset IDs to download - List assetIds; + Optional?> assetIds; /// User ID to download assets from /// @@ -50,7 +50,7 @@ class DownloadInfoDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? userId; + Optional userId; @override bool operator ==(Object other) => identical(this, other) || other is DownloadInfoDto && @@ -72,21 +72,21 @@ class DownloadInfoDto { Map toJson() { final json = {}; - if (this.albumId != null) { - json[r'albumId'] = this.albumId; - } else { - // json[r'albumId'] = null; + if (this.albumId.isPresent) { + final value = this.albumId.value; + json[r'albumId'] = value; } - if (this.archiveSize != null) { - json[r'archiveSize'] = this.archiveSize; - } else { - // json[r'archiveSize'] = null; + if (this.archiveSize.isPresent) { + final value = this.archiveSize.value; + json[r'archiveSize'] = value; } - json[r'assetIds'] = this.assetIds; - if (this.userId != null) { - json[r'userId'] = this.userId; - } else { - // json[r'userId'] = null; + if (this.assetIds.isPresent) { + final value = this.assetIds.value; + json[r'assetIds'] = value; + } + if (this.userId.isPresent) { + final value = this.userId.value; + json[r'userId'] = value; } return json; } @@ -100,12 +100,12 @@ class DownloadInfoDto { final json = value.cast(); return DownloadInfoDto( - albumId: mapValueOfType(json, r'albumId'), - archiveSize: mapValueOfType(json, r'archiveSize'), - assetIds: json[r'assetIds'] is Iterable + albumId: json.containsKey(r'albumId') ? Optional.present(mapValueOfType(json, r'albumId')) : const Optional.absent(), + archiveSize: json.containsKey(r'archiveSize') ? Optional.present(json[r'archiveSize'] == null ? null : int.parse('${json[r'archiveSize']}')) : const Optional.absent(), + assetIds: json.containsKey(r'assetIds') ? Optional.present(json[r'assetIds'] is Iterable ? (json[r'assetIds'] as Iterable).cast().toList(growable: false) - : const [], - userId: mapValueOfType(json, r'userId'), + : const []) : const Optional.absent(), + userId: json.containsKey(r'userId') ? Optional.present(mapValueOfType(json, r'userId')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/download_update.dart b/mobile/openapi/lib/model/download_update.dart index c5feb9df43..08369ef0fb 100644 --- a/mobile/openapi/lib/model/download_update.dart +++ b/mobile/openapi/lib/model/download_update.dart @@ -13,8 +13,8 @@ part of openapi.api; class DownloadUpdate { /// Returns a new [DownloadUpdate] instance. DownloadUpdate({ - this.archiveSize, - this.includeEmbeddedVideos, + this.archiveSize = const Optional.absent(), + this.includeEmbeddedVideos = const Optional.absent(), }); /// Maximum archive size in bytes @@ -27,7 +27,7 @@ class DownloadUpdate { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - int? archiveSize; + Optional archiveSize; /// Whether to include embedded videos in downloads /// @@ -36,7 +36,7 @@ class DownloadUpdate { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? includeEmbeddedVideos; + Optional includeEmbeddedVideos; @override bool operator ==(Object other) => identical(this, other) || other is DownloadUpdate && @@ -54,15 +54,13 @@ class DownloadUpdate { Map toJson() { final json = {}; - if (this.archiveSize != null) { - json[r'archiveSize'] = this.archiveSize; - } else { - // json[r'archiveSize'] = null; + if (this.archiveSize.isPresent) { + final value = this.archiveSize.value; + json[r'archiveSize'] = value; } - if (this.includeEmbeddedVideos != null) { - json[r'includeEmbeddedVideos'] = this.includeEmbeddedVideos; - } else { - // json[r'includeEmbeddedVideos'] = null; + if (this.includeEmbeddedVideos.isPresent) { + final value = this.includeEmbeddedVideos.value; + json[r'includeEmbeddedVideos'] = value; } return json; } @@ -76,8 +74,8 @@ class DownloadUpdate { final json = value.cast(); return DownloadUpdate( - archiveSize: mapValueOfType(json, r'archiveSize'), - includeEmbeddedVideos: mapValueOfType(json, r'includeEmbeddedVideos'), + archiveSize: json.containsKey(r'archiveSize') ? Optional.present(json[r'archiveSize'] == null ? null : int.parse('${json[r'archiveSize']}')) : const Optional.absent(), + includeEmbeddedVideos: json.containsKey(r'includeEmbeddedVideos') ? Optional.present(mapValueOfType(json, r'includeEmbeddedVideos')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/duplicate_detection_config.dart b/mobile/openapi/lib/model/duplicate_detection_config.dart index 43233826ef..d0f016a4f3 100644 --- a/mobile/openapi/lib/model/duplicate_detection_config.dart +++ b/mobile/openapi/lib/model/duplicate_detection_config.dart @@ -57,7 +57,7 @@ class DuplicateDetectionConfig { return DuplicateDetectionConfig( enabled: mapValueOfType(json, r'enabled')!, - maxDistance: (mapValueOfType(json, r'maxDistance')!).toDouble(), + maxDistance: mapValueOfType(json, r'maxDistance')!, ); } return null; diff --git a/mobile/openapi/lib/model/email_notifications_update.dart b/mobile/openapi/lib/model/email_notifications_update.dart index e158e45598..89724e0552 100644 --- a/mobile/openapi/lib/model/email_notifications_update.dart +++ b/mobile/openapi/lib/model/email_notifications_update.dart @@ -13,9 +13,9 @@ part of openapi.api; class EmailNotificationsUpdate { /// Returns a new [EmailNotificationsUpdate] instance. EmailNotificationsUpdate({ - this.albumInvite, - this.albumUpdate, - this.enabled, + this.albumInvite = const Optional.absent(), + this.albumUpdate = const Optional.absent(), + this.enabled = const Optional.absent(), }); /// Whether to receive email notifications for album invites @@ -25,7 +25,7 @@ class EmailNotificationsUpdate { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? albumInvite; + Optional albumInvite; /// Whether to receive email notifications for album updates /// @@ -34,7 +34,7 @@ class EmailNotificationsUpdate { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? albumUpdate; + Optional albumUpdate; /// Whether email notifications are enabled /// @@ -43,7 +43,7 @@ class EmailNotificationsUpdate { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? enabled; + Optional enabled; @override bool operator ==(Object other) => identical(this, other) || other is EmailNotificationsUpdate && @@ -63,20 +63,17 @@ class EmailNotificationsUpdate { Map toJson() { final json = {}; - if (this.albumInvite != null) { - json[r'albumInvite'] = this.albumInvite; - } else { - // json[r'albumInvite'] = null; + if (this.albumInvite.isPresent) { + final value = this.albumInvite.value; + json[r'albumInvite'] = value; } - if (this.albumUpdate != null) { - json[r'albumUpdate'] = this.albumUpdate; - } else { - // json[r'albumUpdate'] = null; + if (this.albumUpdate.isPresent) { + final value = this.albumUpdate.value; + json[r'albumUpdate'] = value; } - if (this.enabled != null) { - json[r'enabled'] = this.enabled; - } else { - // json[r'enabled'] = null; + if (this.enabled.isPresent) { + final value = this.enabled.value; + json[r'enabled'] = value; } return json; } @@ -90,9 +87,9 @@ class EmailNotificationsUpdate { final json = value.cast(); return EmailNotificationsUpdate( - albumInvite: mapValueOfType(json, r'albumInvite'), - albumUpdate: mapValueOfType(json, r'albumUpdate'), - enabled: mapValueOfType(json, r'enabled'), + albumInvite: json.containsKey(r'albumInvite') ? Optional.present(mapValueOfType(json, r'albumInvite')) : const Optional.absent(), + albumUpdate: json.containsKey(r'albumUpdate') ? Optional.present(mapValueOfType(json, r'albumUpdate')) : const Optional.absent(), + enabled: json.containsKey(r'enabled') ? Optional.present(mapValueOfType(json, r'enabled')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/exif_response_dto.dart b/mobile/openapi/lib/model/exif_response_dto.dart index ed5ffd2958..8a0ac0909c 100644 --- a/mobile/openapi/lib/model/exif_response_dto.dart +++ b/mobile/openapi/lib/model/exif_response_dto.dart @@ -13,110 +13,110 @@ part of openapi.api; class ExifResponseDto { /// Returns a new [ExifResponseDto] instance. ExifResponseDto({ - this.city, - this.country, - this.dateTimeOriginal, - this.description, - this.exifImageHeight, - this.exifImageWidth, - this.exposureTime, - this.fNumber, - this.fileSizeInByte, - this.focalLength, - this.iso, - this.latitude, - this.lensModel, - this.longitude, - this.make, - this.model, - this.modifyDate, - this.orientation, - this.projectionType, - this.rating, - this.state, - this.timeZone, + this.city = const Optional.absent(), + this.country = const Optional.absent(), + this.dateTimeOriginal = const Optional.absent(), + this.description = const Optional.absent(), + this.exifImageHeight = const Optional.absent(), + this.exifImageWidth = const Optional.absent(), + this.exposureTime = const Optional.absent(), + this.fNumber = const Optional.absent(), + this.fileSizeInByte = const Optional.absent(), + this.focalLength = const Optional.absent(), + this.iso = const Optional.absent(), + this.latitude = const Optional.absent(), + this.lensModel = const Optional.absent(), + this.longitude = const Optional.absent(), + this.make = const Optional.absent(), + this.model = const Optional.absent(), + this.modifyDate = const Optional.absent(), + this.orientation = const Optional.absent(), + this.projectionType = const Optional.absent(), + this.rating = const Optional.absent(), + this.state = const Optional.absent(), + this.timeZone = const Optional.absent(), }); /// City name - String? city; + Optional city; /// Country name - String? country; + Optional country; /// Original date/time - DateTime? dateTimeOriginal; + Optional dateTimeOriginal; /// Image description - String? description; + Optional description; /// Image height in pixels /// /// Minimum value: 0 /// Maximum value: 9007199254740991 - int? exifImageHeight; + Optional exifImageHeight; /// Image width in pixels /// /// Minimum value: 0 /// Maximum value: 9007199254740991 - int? exifImageWidth; + Optional exifImageWidth; /// Exposure time - String? exposureTime; + Optional exposureTime; /// F-number (aperture) - num? fNumber; + Optional fNumber; /// File size in bytes /// /// Minimum value: 0 /// Maximum value: 9007199254740991 - int? fileSizeInByte; + Optional fileSizeInByte; /// Focal length in mm - num? focalLength; + Optional focalLength; /// ISO sensitivity /// /// Minimum value: -9007199254740991 /// Maximum value: 9007199254740991 - int? iso; + Optional iso; /// GPS latitude - num? latitude; + Optional latitude; /// Lens model - String? lensModel; + Optional lensModel; /// GPS longitude - num? longitude; + Optional longitude; /// Camera make - String? make; + Optional make; /// Camera model - String? model; + Optional model; /// Modification date/time - DateTime? modifyDate; + Optional modifyDate; /// Image orientation - String? orientation; + Optional orientation; /// Projection type - String? projectionType; + Optional projectionType; /// Rating /// /// Minimum value: -9007199254740991 /// Maximum value: 9007199254740991 - int? rating; + Optional rating; /// State/province name - String? state; + Optional state; /// Time zone - String? timeZone; + Optional timeZone; @override bool operator ==(Object other) => identical(this, other) || other is ExifResponseDto && @@ -174,115 +174,93 @@ class ExifResponseDto { Map toJson() { final json = {}; - if (this.city != null) { - json[r'city'] = this.city; - } else { - // json[r'city'] = null; + if (this.city.isPresent) { + final value = this.city.value; + json[r'city'] = value; } - if (this.country != null) { - json[r'country'] = this.country; - } else { - // json[r'country'] = null; + if (this.country.isPresent) { + final value = this.country.value; + json[r'country'] = value; } - if (this.dateTimeOriginal != null) { - json[r'dateTimeOriginal'] = this.dateTimeOriginal!.toUtc().toIso8601String(); - } else { - // json[r'dateTimeOriginal'] = null; + if (this.dateTimeOriginal.isPresent) { + final value = this.dateTimeOriginal.value; + json[r'dateTimeOriginal'] = value == null ? null : value.toUtc().toIso8601String(); } - if (this.description != null) { - json[r'description'] = this.description; - } else { - // json[r'description'] = null; + if (this.description.isPresent) { + final value = this.description.value; + json[r'description'] = value; } - if (this.exifImageHeight != null) { - json[r'exifImageHeight'] = this.exifImageHeight; - } else { - // json[r'exifImageHeight'] = null; + if (this.exifImageHeight.isPresent) { + final value = this.exifImageHeight.value; + json[r'exifImageHeight'] = value; } - if (this.exifImageWidth != null) { - json[r'exifImageWidth'] = this.exifImageWidth; - } else { - // json[r'exifImageWidth'] = null; + if (this.exifImageWidth.isPresent) { + final value = this.exifImageWidth.value; + json[r'exifImageWidth'] = value; } - if (this.exposureTime != null) { - json[r'exposureTime'] = this.exposureTime; - } else { - // json[r'exposureTime'] = null; + if (this.exposureTime.isPresent) { + final value = this.exposureTime.value; + json[r'exposureTime'] = value; } - if (this.fNumber != null) { - json[r'fNumber'] = this.fNumber; - } else { - // json[r'fNumber'] = null; + if (this.fNumber.isPresent) { + final value = this.fNumber.value; + json[r'fNumber'] = value; } - if (this.fileSizeInByte != null) { - json[r'fileSizeInByte'] = this.fileSizeInByte; - } else { - // json[r'fileSizeInByte'] = null; + if (this.fileSizeInByte.isPresent) { + final value = this.fileSizeInByte.value; + json[r'fileSizeInByte'] = value; } - if (this.focalLength != null) { - json[r'focalLength'] = this.focalLength; - } else { - // json[r'focalLength'] = null; + if (this.focalLength.isPresent) { + final value = this.focalLength.value; + json[r'focalLength'] = value; } - if (this.iso != null) { - json[r'iso'] = this.iso; - } else { - // json[r'iso'] = null; + if (this.iso.isPresent) { + final value = this.iso.value; + json[r'iso'] = value; } - if (this.latitude != null) { - json[r'latitude'] = this.latitude; - } else { - // json[r'latitude'] = null; + if (this.latitude.isPresent) { + final value = this.latitude.value; + json[r'latitude'] = value; } - if (this.lensModel != null) { - json[r'lensModel'] = this.lensModel; - } else { - // json[r'lensModel'] = null; + if (this.lensModel.isPresent) { + final value = this.lensModel.value; + json[r'lensModel'] = value; } - if (this.longitude != null) { - json[r'longitude'] = this.longitude; - } else { - // json[r'longitude'] = null; + if (this.longitude.isPresent) { + final value = this.longitude.value; + json[r'longitude'] = value; } - if (this.make != null) { - json[r'make'] = this.make; - } else { - // json[r'make'] = null; + if (this.make.isPresent) { + final value = this.make.value; + json[r'make'] = value; } - if (this.model != null) { - json[r'model'] = this.model; - } else { - // json[r'model'] = null; + if (this.model.isPresent) { + final value = this.model.value; + json[r'model'] = value; } - if (this.modifyDate != null) { - json[r'modifyDate'] = this.modifyDate!.toUtc().toIso8601String(); - } else { - // json[r'modifyDate'] = null; + if (this.modifyDate.isPresent) { + final value = this.modifyDate.value; + json[r'modifyDate'] = value == null ? null : value.toUtc().toIso8601String(); } - if (this.orientation != null) { - json[r'orientation'] = this.orientation; - } else { - // json[r'orientation'] = null; + if (this.orientation.isPresent) { + final value = this.orientation.value; + json[r'orientation'] = value; } - if (this.projectionType != null) { - json[r'projectionType'] = this.projectionType; - } else { - // json[r'projectionType'] = null; + if (this.projectionType.isPresent) { + final value = this.projectionType.value; + json[r'projectionType'] = value; } - if (this.rating != null) { - json[r'rating'] = this.rating; - } else { - // json[r'rating'] = null; + if (this.rating.isPresent) { + final value = this.rating.value; + json[r'rating'] = value; } - if (this.state != null) { - json[r'state'] = this.state; - } else { - // json[r'state'] = null; + if (this.state.isPresent) { + final value = this.state.value; + json[r'state'] = value; } - if (this.timeZone != null) { - json[r'timeZone'] = this.timeZone; - } else { - // json[r'timeZone'] = null; + if (this.timeZone.isPresent) { + final value = this.timeZone.value; + json[r'timeZone'] = value; } return json; } @@ -296,36 +274,28 @@ class ExifResponseDto { final json = value.cast(); return ExifResponseDto( - city: mapValueOfType(json, r'city'), - country: mapValueOfType(json, r'country'), - dateTimeOriginal: mapDateTime(json, r'dateTimeOriginal', r''), - description: mapValueOfType(json, r'description'), - exifImageHeight: mapValueOfType(json, r'exifImageHeight'), - exifImageWidth: mapValueOfType(json, r'exifImageWidth'), - exposureTime: mapValueOfType(json, r'exposureTime'), - fNumber: json[r'fNumber'] == null - ? null - : num.parse('${json[r'fNumber']}'), - fileSizeInByte: mapValueOfType(json, r'fileSizeInByte'), - focalLength: json[r'focalLength'] == null - ? null - : num.parse('${json[r'focalLength']}'), - iso: mapValueOfType(json, r'iso'), - latitude: json[r'latitude'] == null - ? null - : num.parse('${json[r'latitude']}'), - lensModel: mapValueOfType(json, r'lensModel'), - longitude: json[r'longitude'] == null - ? null - : num.parse('${json[r'longitude']}'), - make: mapValueOfType(json, r'make'), - model: mapValueOfType(json, r'model'), - modifyDate: mapDateTime(json, r'modifyDate', r''), - orientation: mapValueOfType(json, r'orientation'), - projectionType: mapValueOfType(json, r'projectionType'), - rating: mapValueOfType(json, r'rating'), - state: mapValueOfType(json, r'state'), - timeZone: mapValueOfType(json, r'timeZone'), + city: json.containsKey(r'city') ? Optional.present(mapValueOfType(json, r'city')) : const Optional.absent(), + country: json.containsKey(r'country') ? Optional.present(mapValueOfType(json, r'country')) : const Optional.absent(), + dateTimeOriginal: json.containsKey(r'dateTimeOriginal') ? Optional.present(mapDateTime(json, r'dateTimeOriginal', r'')) : const Optional.absent(), + description: json.containsKey(r'description') ? Optional.present(mapValueOfType(json, r'description')) : const Optional.absent(), + exifImageHeight: json.containsKey(r'exifImageHeight') ? Optional.present(json[r'exifImageHeight'] == null ? null : int.parse('${json[r'exifImageHeight']}')) : const Optional.absent(), + exifImageWidth: json.containsKey(r'exifImageWidth') ? Optional.present(json[r'exifImageWidth'] == null ? null : int.parse('${json[r'exifImageWidth']}')) : const Optional.absent(), + exposureTime: json.containsKey(r'exposureTime') ? Optional.present(mapValueOfType(json, r'exposureTime')) : const Optional.absent(), + fNumber: json.containsKey(r'fNumber') ? Optional.present(json[r'fNumber'] == null ? null : num.parse('${json[r'fNumber']}')) : const Optional.absent(), + fileSizeInByte: json.containsKey(r'fileSizeInByte') ? Optional.present(json[r'fileSizeInByte'] == null ? null : int.parse('${json[r'fileSizeInByte']}')) : const Optional.absent(), + focalLength: json.containsKey(r'focalLength') ? Optional.present(json[r'focalLength'] == null ? null : num.parse('${json[r'focalLength']}')) : const Optional.absent(), + iso: json.containsKey(r'iso') ? Optional.present(json[r'iso'] == null ? null : int.parse('${json[r'iso']}')) : const Optional.absent(), + latitude: json.containsKey(r'latitude') ? Optional.present(json[r'latitude'] == null ? null : num.parse('${json[r'latitude']}')) : const Optional.absent(), + lensModel: json.containsKey(r'lensModel') ? Optional.present(mapValueOfType(json, r'lensModel')) : const Optional.absent(), + longitude: json.containsKey(r'longitude') ? Optional.present(json[r'longitude'] == null ? null : num.parse('${json[r'longitude']}')) : const Optional.absent(), + make: json.containsKey(r'make') ? Optional.present(mapValueOfType(json, r'make')) : const Optional.absent(), + model: json.containsKey(r'model') ? Optional.present(mapValueOfType(json, r'model')) : const Optional.absent(), + modifyDate: json.containsKey(r'modifyDate') ? Optional.present(mapDateTime(json, r'modifyDate', r'')) : const Optional.absent(), + orientation: json.containsKey(r'orientation') ? Optional.present(mapValueOfType(json, r'orientation')) : const Optional.absent(), + projectionType: json.containsKey(r'projectionType') ? Optional.present(mapValueOfType(json, r'projectionType')) : const Optional.absent(), + rating: json.containsKey(r'rating') ? Optional.present(json[r'rating'] == null ? null : int.parse('${json[r'rating']}')) : const Optional.absent(), + state: json.containsKey(r'state') ? Optional.present(mapValueOfType(json, r'state')) : const Optional.absent(), + timeZone: json.containsKey(r'timeZone') ? Optional.present(mapValueOfType(json, r'timeZone')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/facial_recognition_config.dart b/mobile/openapi/lib/model/facial_recognition_config.dart index 66cb542ccf..c5f477e1d5 100644 --- a/mobile/openapi/lib/model/facial_recognition_config.dart +++ b/mobile/openapi/lib/model/facial_recognition_config.dart @@ -84,9 +84,9 @@ class FacialRecognitionConfig { return FacialRecognitionConfig( enabled: mapValueOfType(json, r'enabled')!, - maxDistance: (mapValueOfType(json, r'maxDistance')!).toDouble(), + maxDistance: mapValueOfType(json, r'maxDistance')!, minFaces: mapValueOfType(json, r'minFaces')!, - minScore: (mapValueOfType(json, r'minScore')!).toDouble(), + minScore: mapValueOfType(json, r'minScore')!, modelName: mapValueOfType(json, r'modelName')!, ); } diff --git a/mobile/openapi/lib/model/folders_update.dart b/mobile/openapi/lib/model/folders_update.dart index edd58014d4..2ce0cde807 100644 --- a/mobile/openapi/lib/model/folders_update.dart +++ b/mobile/openapi/lib/model/folders_update.dart @@ -13,8 +13,8 @@ part of openapi.api; class FoldersUpdate { /// Returns a new [FoldersUpdate] instance. FoldersUpdate({ - this.enabled, - this.sidebarWeb, + this.enabled = const Optional.absent(), + this.sidebarWeb = const Optional.absent(), }); /// Whether folders are enabled @@ -24,7 +24,7 @@ class FoldersUpdate { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? enabled; + Optional enabled; /// Whether folders appear in web sidebar /// @@ -33,7 +33,7 @@ class FoldersUpdate { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? sidebarWeb; + Optional sidebarWeb; @override bool operator ==(Object other) => identical(this, other) || other is FoldersUpdate && @@ -51,15 +51,13 @@ class FoldersUpdate { Map toJson() { final json = {}; - if (this.enabled != null) { - json[r'enabled'] = this.enabled; - } else { - // json[r'enabled'] = null; + if (this.enabled.isPresent) { + final value = this.enabled.value; + json[r'enabled'] = value; } - if (this.sidebarWeb != null) { - json[r'sidebarWeb'] = this.sidebarWeb; - } else { - // json[r'sidebarWeb'] = null; + if (this.sidebarWeb.isPresent) { + final value = this.sidebarWeb.value; + json[r'sidebarWeb'] = value; } return json; } @@ -73,8 +71,8 @@ class FoldersUpdate { final json = value.cast(); return FoldersUpdate( - enabled: mapValueOfType(json, r'enabled'), - sidebarWeb: mapValueOfType(json, r'sidebarWeb'), + enabled: json.containsKey(r'enabled') ? Optional.present(mapValueOfType(json, r'enabled')) : const Optional.absent(), + sidebarWeb: json.containsKey(r'sidebarWeb') ? Optional.present(mapValueOfType(json, r'sidebarWeb')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/job_name.dart b/mobile/openapi/lib/model/job_name.dart index 08f70569f8..435cffd623 100644 --- a/mobile/openapi/lib/model/job_name.dart +++ b/mobile/openapi/lib/model/job_name.dart @@ -52,6 +52,7 @@ class JobName { static const librarySyncFilesQueueAll = JobName._(r'LibrarySyncFilesQueueAll'); static const librarySyncFiles = JobName._(r'LibrarySyncFiles'); static const libraryScanQueueAll = JobName._(r'LibraryScanQueueAll'); + static const hlsSessionCleanup = JobName._(r'HlsSessionCleanup'); static const memoryCleanup = JobName._(r'MemoryCleanup'); static const memoryGenerate = JobName._(r'MemoryGenerate'); static const notificationsCleanup = JobName._(r'NotificationsCleanup'); @@ -77,7 +78,7 @@ class JobName { static const versionCheck = JobName._(r'VersionCheck'); static const ocrQueueAll = JobName._(r'OcrQueueAll'); static const ocr = JobName._(r'Ocr'); - static const workflowRun = JobName._(r'WorkflowRun'); + static const workflowAssetTrigger = JobName._(r'WorkflowAssetTrigger'); /// List of all possible values in this [enum][JobName]. static const values = [ @@ -110,6 +111,7 @@ class JobName { librarySyncFilesQueueAll, librarySyncFiles, libraryScanQueueAll, + hlsSessionCleanup, memoryCleanup, memoryGenerate, notificationsCleanup, @@ -135,7 +137,7 @@ class JobName { versionCheck, ocrQueueAll, ocr, - workflowRun, + workflowAssetTrigger, ]; static JobName? fromJson(dynamic value) => JobNameTypeTransformer().decode(value); @@ -203,6 +205,7 @@ class JobNameTypeTransformer { case r'LibrarySyncFilesQueueAll': return JobName.librarySyncFilesQueueAll; case r'LibrarySyncFiles': return JobName.librarySyncFiles; case r'LibraryScanQueueAll': return JobName.libraryScanQueueAll; + case r'HlsSessionCleanup': return JobName.hlsSessionCleanup; case r'MemoryCleanup': return JobName.memoryCleanup; case r'MemoryGenerate': return JobName.memoryGenerate; case r'NotificationsCleanup': return JobName.notificationsCleanup; @@ -228,7 +231,7 @@ class JobNameTypeTransformer { case r'VersionCheck': return JobName.versionCheck; case r'OcrQueueAll': return JobName.ocrQueueAll; case r'Ocr': return JobName.ocr; - case r'WorkflowRun': return JobName.workflowRun; + case r'WorkflowAssetTrigger': return JobName.workflowAssetTrigger; default: if (!allowNull) { throw ArgumentError('Unknown enum value to decode: $data'); diff --git a/mobile/openapi/lib/model/library_response_dto.dart b/mobile/openapi/lib/model/library_response_dto.dart index 88ebceae24..e52a752656 100644 --- a/mobile/openapi/lib/model/library_response_dto.dart +++ b/mobile/openapi/lib/model/library_response_dto.dart @@ -98,7 +98,7 @@ class LibraryResponseDto { ? this.refreshedAt!.millisecondsSinceEpoch : this.refreshedAt!.toUtc().toIso8601String(); } else { - // json[r'refreshedAt'] = null; + json[r'refreshedAt'] = null; } json[r'updatedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') ? this.updatedAt.millisecondsSinceEpoch diff --git a/mobile/openapi/lib/model/maintenance_login_dto.dart b/mobile/openapi/lib/model/maintenance_login_dto.dart index 64cf6b234b..eaa91ae738 100644 --- a/mobile/openapi/lib/model/maintenance_login_dto.dart +++ b/mobile/openapi/lib/model/maintenance_login_dto.dart @@ -13,7 +13,7 @@ part of openapi.api; class MaintenanceLoginDto { /// Returns a new [MaintenanceLoginDto] instance. MaintenanceLoginDto({ - this.token, + this.token = const Optional.absent(), }); /// Maintenance token @@ -23,7 +23,7 @@ class MaintenanceLoginDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? token; + Optional token; @override bool operator ==(Object other) => identical(this, other) || other is MaintenanceLoginDto && @@ -39,10 +39,9 @@ class MaintenanceLoginDto { Map toJson() { final json = {}; - if (this.token != null) { - json[r'token'] = this.token; - } else { - // json[r'token'] = null; + if (this.token.isPresent) { + final value = this.token.value; + json[r'token'] = value; } return json; } @@ -56,7 +55,7 @@ class MaintenanceLoginDto { final json = value.cast(); return MaintenanceLoginDto( - token: mapValueOfType(json, r'token'), + token: json.containsKey(r'token') ? Optional.present(mapValueOfType(json, r'token')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/maintenance_status_response_dto.dart b/mobile/openapi/lib/model/maintenance_status_response_dto.dart index c1c94acd91..82ad12d340 100644 --- a/mobile/openapi/lib/model/maintenance_status_response_dto.dart +++ b/mobile/openapi/lib/model/maintenance_status_response_dto.dart @@ -15,9 +15,9 @@ class MaintenanceStatusResponseDto { MaintenanceStatusResponseDto({ required this.action, required this.active, - this.error, - this.progress, - this.task, + this.error = const Optional.absent(), + this.progress = const Optional.absent(), + this.task = const Optional.absent(), }); MaintenanceAction action; @@ -30,7 +30,7 @@ class MaintenanceStatusResponseDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? error; + Optional error; /// Minimum value: -9007199254740991 /// Maximum value: 9007199254740991 @@ -40,7 +40,7 @@ class MaintenanceStatusResponseDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - int? progress; + Optional progress; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -48,7 +48,7 @@ class MaintenanceStatusResponseDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? task; + Optional task; @override bool operator ==(Object other) => identical(this, other) || other is MaintenanceStatusResponseDto && @@ -74,20 +74,17 @@ class MaintenanceStatusResponseDto { final json = {}; json[r'action'] = this.action; json[r'active'] = this.active; - if (this.error != null) { - json[r'error'] = this.error; - } else { - // json[r'error'] = null; + if (this.error.isPresent) { + final value = this.error.value; + json[r'error'] = value; } - if (this.progress != null) { - json[r'progress'] = this.progress; - } else { - // json[r'progress'] = null; + if (this.progress.isPresent) { + final value = this.progress.value; + json[r'progress'] = value; } - if (this.task != null) { - json[r'task'] = this.task; - } else { - // json[r'task'] = null; + if (this.task.isPresent) { + final value = this.task.value; + json[r'task'] = value; } return json; } @@ -103,9 +100,9 @@ class MaintenanceStatusResponseDto { return MaintenanceStatusResponseDto( action: MaintenanceAction.fromJson(json[r'action'])!, active: mapValueOfType(json, r'active')!, - error: mapValueOfType(json, r'error'), - progress: mapValueOfType(json, r'progress'), - task: mapValueOfType(json, r'task'), + error: json.containsKey(r'error') ? Optional.present(mapValueOfType(json, r'error')) : const Optional.absent(), + progress: json.containsKey(r'progress') ? Optional.present(json[r'progress'] == null ? null : int.parse('${json[r'progress']}')) : const Optional.absent(), + task: json.containsKey(r'task') ? Optional.present(mapValueOfType(json, r'task')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/map_marker_response_dto.dart b/mobile/openapi/lib/model/map_marker_response_dto.dart index c0a47a5458..3f19c21b6b 100644 --- a/mobile/openapi/lib/model/map_marker_response_dto.dart +++ b/mobile/openapi/lib/model/map_marker_response_dto.dart @@ -66,12 +66,12 @@ class MapMarkerResponseDto { if (this.city != null) { json[r'city'] = this.city; } else { - // json[r'city'] = null; + json[r'city'] = null; } if (this.country != null) { json[r'country'] = this.country; } else { - // json[r'country'] = null; + json[r'country'] = null; } json[r'id'] = this.id; json[r'lat'] = this.lat; @@ -79,7 +79,7 @@ class MapMarkerResponseDto { if (this.state != null) { json[r'state'] = this.state; } else { - // json[r'state'] = null; + json[r'state'] = null; } return json; } @@ -96,8 +96,8 @@ class MapMarkerResponseDto { city: mapValueOfType(json, r'city'), country: mapValueOfType(json, r'country'), id: mapValueOfType(json, r'id')!, - lat: (mapValueOfType(json, r'lat')!).toDouble(), - lon: (mapValueOfType(json, r'lon')!).toDouble(), + lat: mapValueOfType(json, r'lat')!, + lon: mapValueOfType(json, r'lon')!, state: mapValueOfType(json, r'state'), ); } diff --git a/mobile/openapi/lib/model/map_reverse_geocode_response_dto.dart b/mobile/openapi/lib/model/map_reverse_geocode_response_dto.dart index 85435485e6..0fc30f2b88 100644 --- a/mobile/openapi/lib/model/map_reverse_geocode_response_dto.dart +++ b/mobile/openapi/lib/model/map_reverse_geocode_response_dto.dart @@ -48,17 +48,17 @@ class MapReverseGeocodeResponseDto { if (this.city != null) { json[r'city'] = this.city; } else { - // json[r'city'] = null; + json[r'city'] = null; } if (this.country != null) { json[r'country'] = this.country; } else { - // json[r'country'] = null; + json[r'country'] = null; } if (this.state != null) { json[r'state'] = this.state; } else { - // json[r'state'] = null; + json[r'state'] = null; } return json; } diff --git a/mobile/openapi/lib/model/memories_update.dart b/mobile/openapi/lib/model/memories_update.dart index ede9910d74..350cf19182 100644 --- a/mobile/openapi/lib/model/memories_update.dart +++ b/mobile/openapi/lib/model/memories_update.dart @@ -13,8 +13,8 @@ part of openapi.api; class MemoriesUpdate { /// Returns a new [MemoriesUpdate] instance. MemoriesUpdate({ - this.duration, - this.enabled, + this.duration = const Optional.absent(), + this.enabled = const Optional.absent(), }); /// Memory duration in seconds @@ -27,7 +27,7 @@ class MemoriesUpdate { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - int? duration; + Optional duration; /// Whether memories are enabled /// @@ -36,7 +36,7 @@ class MemoriesUpdate { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? enabled; + Optional enabled; @override bool operator ==(Object other) => identical(this, other) || other is MemoriesUpdate && @@ -54,15 +54,13 @@ class MemoriesUpdate { Map toJson() { final json = {}; - if (this.duration != null) { - json[r'duration'] = this.duration; - } else { - // json[r'duration'] = null; + if (this.duration.isPresent) { + final value = this.duration.value; + json[r'duration'] = value; } - if (this.enabled != null) { - json[r'enabled'] = this.enabled; - } else { - // json[r'enabled'] = null; + if (this.enabled.isPresent) { + final value = this.enabled.value; + json[r'enabled'] = value; } return json; } @@ -76,8 +74,8 @@ class MemoriesUpdate { final json = value.cast(); return MemoriesUpdate( - duration: mapValueOfType(json, r'duration'), - enabled: mapValueOfType(json, r'enabled'), + duration: json.containsKey(r'duration') ? Optional.present(json[r'duration'] == null ? null : int.parse('${json[r'duration']}')) : const Optional.absent(), + enabled: json.containsKey(r'enabled') ? Optional.present(mapValueOfType(json, r'enabled')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/memory_create_dto.dart b/mobile/openapi/lib/model/memory_create_dto.dart index b906f6dd1d..a7be9bf70d 100644 --- a/mobile/openapi/lib/model/memory_create_dto.dart +++ b/mobile/openapi/lib/model/memory_create_dto.dart @@ -13,18 +13,18 @@ part of openapi.api; class MemoryCreateDto { /// Returns a new [MemoryCreateDto] instance. MemoryCreateDto({ - this.assetIds = const [], + this.assetIds = const Optional.present(const []), required this.data, - this.hideAt, - this.isSaved, + this.hideAt = const Optional.absent(), + this.isSaved = const Optional.absent(), required this.memoryAt, - this.seenAt, - this.showAt, + this.seenAt = const Optional.absent(), + this.showAt = const Optional.absent(), required this.type, }); /// Asset IDs to associate with memory - List assetIds; + Optional?> assetIds; OnThisDayDto data; @@ -35,7 +35,7 @@ class MemoryCreateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - DateTime? hideAt; + Optional hideAt; /// Is memory saved /// @@ -44,7 +44,7 @@ class MemoryCreateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? isSaved; + Optional isSaved; /// Memory date DateTime memoryAt; @@ -56,7 +56,7 @@ class MemoryCreateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - DateTime? seenAt; + Optional seenAt; /// Date when memory should be shown /// @@ -65,7 +65,7 @@ class MemoryCreateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - DateTime? showAt; + Optional showAt; MemoryType type; @@ -97,36 +97,35 @@ class MemoryCreateDto { Map toJson() { final json = {}; - json[r'assetIds'] = this.assetIds; - json[r'data'] = this.data; - if (this.hideAt != null) { - json[r'hideAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.hideAt!.millisecondsSinceEpoch - : this.hideAt!.toUtc().toIso8601String(); - } else { - // json[r'hideAt'] = null; + if (this.assetIds.isPresent) { + final value = this.assetIds.value; + json[r'assetIds'] = value; } - if (this.isSaved != null) { - json[r'isSaved'] = this.isSaved; - } else { - // json[r'isSaved'] = null; + json[r'data'] = this.data; + if (this.hideAt.isPresent) { + final value = this.hideAt.value; + json[r'hideAt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); + } + if (this.isSaved.isPresent) { + final value = this.isSaved.value; + json[r'isSaved'] = value; } json[r'memoryAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') ? this.memoryAt.millisecondsSinceEpoch : this.memoryAt.toUtc().toIso8601String(); - if (this.seenAt != null) { - json[r'seenAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.seenAt!.millisecondsSinceEpoch - : this.seenAt!.toUtc().toIso8601String(); - } else { - // json[r'seenAt'] = null; + if (this.seenAt.isPresent) { + final value = this.seenAt.value; + json[r'seenAt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.showAt != null) { - json[r'showAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.showAt!.millisecondsSinceEpoch - : this.showAt!.toUtc().toIso8601String(); - } else { - // json[r'showAt'] = null; + if (this.showAt.isPresent) { + final value = this.showAt.value; + json[r'showAt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } json[r'type'] = this.type; return json; @@ -141,15 +140,15 @@ class MemoryCreateDto { final json = value.cast(); return MemoryCreateDto( - assetIds: json[r'assetIds'] is Iterable + assetIds: json.containsKey(r'assetIds') ? Optional.present(json[r'assetIds'] is Iterable ? (json[r'assetIds'] as Iterable).cast().toList(growable: false) - : const [], + : const []) : const Optional.absent(), data: OnThisDayDto.fromJson(json[r'data'])!, - hideAt: mapDateTime(json, r'hideAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - isSaved: mapValueOfType(json, r'isSaved'), + hideAt: json.containsKey(r'hideAt') ? Optional.present(mapDateTime(json, r'hideAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + isSaved: json.containsKey(r'isSaved') ? Optional.present(mapValueOfType(json, r'isSaved')) : const Optional.absent(), memoryAt: mapDateTime(json, r'memoryAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')!, - seenAt: mapDateTime(json, r'seenAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - showAt: mapDateTime(json, r'showAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), + seenAt: json.containsKey(r'seenAt') ? Optional.present(mapDateTime(json, r'seenAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + showAt: json.containsKey(r'showAt') ? Optional.present(mapDateTime(json, r'showAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), type: MemoryType.fromJson(json[r'type'])!, ); } diff --git a/mobile/openapi/lib/model/memory_response_dto.dart b/mobile/openapi/lib/model/memory_response_dto.dart index e736667d57..799c692955 100644 --- a/mobile/openapi/lib/model/memory_response_dto.dart +++ b/mobile/openapi/lib/model/memory_response_dto.dart @@ -16,14 +16,14 @@ class MemoryResponseDto { this.assets = const [], required this.createdAt, required this.data, - this.deletedAt, - this.hideAt, + this.deletedAt = const Optional.absent(), + this.hideAt = const Optional.absent(), required this.id, required this.isSaved, required this.memoryAt, required this.ownerId, - this.seenAt, - this.showAt, + this.seenAt = const Optional.absent(), + this.showAt = const Optional.absent(), required this.type, required this.updatedAt, }); @@ -42,7 +42,7 @@ class MemoryResponseDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - DateTime? deletedAt; + Optional deletedAt; /// Date when memory should be hidden /// @@ -51,7 +51,7 @@ class MemoryResponseDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - DateTime? hideAt; + Optional hideAt; /// Memory ID String id; @@ -72,7 +72,7 @@ class MemoryResponseDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - DateTime? seenAt; + Optional seenAt; /// Date when memory should be shown /// @@ -81,7 +81,7 @@ class MemoryResponseDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - DateTime? showAt; + Optional showAt; MemoryType type; @@ -131,19 +131,17 @@ class MemoryResponseDto { ? this.createdAt.millisecondsSinceEpoch : this.createdAt.toUtc().toIso8601String(); json[r'data'] = this.data; - if (this.deletedAt != null) { - json[r'deletedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.deletedAt!.millisecondsSinceEpoch - : this.deletedAt!.toUtc().toIso8601String(); - } else { - // json[r'deletedAt'] = null; + if (this.deletedAt.isPresent) { + final value = this.deletedAt.value; + json[r'deletedAt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.hideAt != null) { - json[r'hideAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.hideAt!.millisecondsSinceEpoch - : this.hideAt!.toUtc().toIso8601String(); - } else { - // json[r'hideAt'] = null; + if (this.hideAt.isPresent) { + final value = this.hideAt.value; + json[r'hideAt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } json[r'id'] = this.id; json[r'isSaved'] = this.isSaved; @@ -151,19 +149,17 @@ class MemoryResponseDto { ? this.memoryAt.millisecondsSinceEpoch : this.memoryAt.toUtc().toIso8601String(); json[r'ownerId'] = this.ownerId; - if (this.seenAt != null) { - json[r'seenAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.seenAt!.millisecondsSinceEpoch - : this.seenAt!.toUtc().toIso8601String(); - } else { - // json[r'seenAt'] = null; + if (this.seenAt.isPresent) { + final value = this.seenAt.value; + json[r'seenAt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.showAt != null) { - json[r'showAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.showAt!.millisecondsSinceEpoch - : this.showAt!.toUtc().toIso8601String(); - } else { - // json[r'showAt'] = null; + if (this.showAt.isPresent) { + final value = this.showAt.value; + json[r'showAt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } json[r'type'] = this.type; json[r'updatedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') @@ -184,14 +180,14 @@ class MemoryResponseDto { assets: AssetResponseDto.listFromJson(json[r'assets']), createdAt: mapDateTime(json, r'createdAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')!, data: OnThisDayDto.fromJson(json[r'data'])!, - deletedAt: mapDateTime(json, r'deletedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - hideAt: mapDateTime(json, r'hideAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), + deletedAt: json.containsKey(r'deletedAt') ? Optional.present(mapDateTime(json, r'deletedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + hideAt: json.containsKey(r'hideAt') ? Optional.present(mapDateTime(json, r'hideAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), id: mapValueOfType(json, r'id')!, isSaved: mapValueOfType(json, r'isSaved')!, memoryAt: mapDateTime(json, r'memoryAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')!, ownerId: mapValueOfType(json, r'ownerId')!, - seenAt: mapDateTime(json, r'seenAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - showAt: mapDateTime(json, r'showAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), + seenAt: json.containsKey(r'seenAt') ? Optional.present(mapDateTime(json, r'seenAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + showAt: json.containsKey(r'showAt') ? Optional.present(mapDateTime(json, r'showAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), type: MemoryType.fromJson(json[r'type'])!, updatedAt: mapDateTime(json, r'updatedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')!, ); diff --git a/mobile/openapi/lib/model/memory_update_dto.dart b/mobile/openapi/lib/model/memory_update_dto.dart index d8d7e9643b..e39506b78e 100644 --- a/mobile/openapi/lib/model/memory_update_dto.dart +++ b/mobile/openapi/lib/model/memory_update_dto.dart @@ -13,9 +13,9 @@ part of openapi.api; class MemoryUpdateDto { /// Returns a new [MemoryUpdateDto] instance. MemoryUpdateDto({ - this.isSaved, - this.memoryAt, - this.seenAt, + this.isSaved = const Optional.absent(), + this.memoryAt = const Optional.absent(), + this.seenAt = const Optional.absent(), }); /// Is memory saved @@ -25,7 +25,7 @@ class MemoryUpdateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? isSaved; + Optional isSaved; /// Memory date /// @@ -34,7 +34,7 @@ class MemoryUpdateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - DateTime? memoryAt; + Optional memoryAt; /// Date when memory was seen /// @@ -43,7 +43,7 @@ class MemoryUpdateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - DateTime? seenAt; + Optional seenAt; @override bool operator ==(Object other) => identical(this, other) || other is MemoryUpdateDto && @@ -63,24 +63,21 @@ class MemoryUpdateDto { Map toJson() { final json = {}; - if (this.isSaved != null) { - json[r'isSaved'] = this.isSaved; - } else { - // json[r'isSaved'] = null; + if (this.isSaved.isPresent) { + final value = this.isSaved.value; + json[r'isSaved'] = value; } - if (this.memoryAt != null) { - json[r'memoryAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.memoryAt!.millisecondsSinceEpoch - : this.memoryAt!.toUtc().toIso8601String(); - } else { - // json[r'memoryAt'] = null; + if (this.memoryAt.isPresent) { + final value = this.memoryAt.value; + json[r'memoryAt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.seenAt != null) { - json[r'seenAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.seenAt!.millisecondsSinceEpoch - : this.seenAt!.toUtc().toIso8601String(); - } else { - // json[r'seenAt'] = null; + if (this.seenAt.isPresent) { + final value = this.seenAt.value; + json[r'seenAt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } return json; } @@ -94,9 +91,9 @@ class MemoryUpdateDto { final json = value.cast(); return MemoryUpdateDto( - isSaved: mapValueOfType(json, r'isSaved'), - memoryAt: mapDateTime(json, r'memoryAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - seenAt: mapDateTime(json, r'seenAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), + isSaved: json.containsKey(r'isSaved') ? Optional.present(mapValueOfType(json, r'isSaved')) : const Optional.absent(), + memoryAt: json.containsKey(r'memoryAt') ? Optional.present(mapDateTime(json, r'memoryAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + seenAt: json.containsKey(r'seenAt') ? Optional.present(mapDateTime(json, r'seenAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/metadata_search_dto.dart b/mobile/openapi/lib/model/metadata_search_dto.dart index 29b1d5b68d..0d93ed3173 100644 --- a/mobile/openapi/lib/model/metadata_search_dto.dart +++ b/mobile/openapi/lib/model/metadata_search_dto.dart @@ -13,52 +13,52 @@ part of openapi.api; class MetadataSearchDto { /// Returns a new [MetadataSearchDto] instance. MetadataSearchDto({ - this.albumIds = const [], - this.checksum, - this.city, - this.country, - this.createdAfter, - this.createdBefore, - this.description, - this.encodedVideoPath, - this.id, - this.isEncoded, - this.isFavorite, - this.isMotion, - this.isNotInAlbum, - this.isOffline, - this.lensModel, - this.libraryId, - this.make, - this.model, - this.ocr, - this.order, - this.originalFileName, - this.originalPath, - this.page, - this.personIds = const [], - this.previewPath, - this.rating, - this.size, - this.state, - this.tagIds = const [], - this.takenAfter, - this.takenBefore, - this.thumbnailPath, - this.trashedAfter, - this.trashedBefore, - this.type, - this.updatedAfter, - this.updatedBefore, - this.visibility, - this.withDeleted, - this.withExif, - this.withPeople, - this.withStacked, + this.albumIds = const Optional.present(const []), + this.checksum = const Optional.absent(), + this.city = const Optional.absent(), + this.country = const Optional.absent(), + this.createdAfter = const Optional.absent(), + this.createdBefore = const Optional.absent(), + this.description = const Optional.absent(), + this.encodedVideoPath = const Optional.absent(), + this.id = const Optional.absent(), + this.isEncoded = const Optional.absent(), + this.isFavorite = const Optional.absent(), + this.isMotion = const Optional.absent(), + this.isNotInAlbum = const Optional.absent(), + this.isOffline = const Optional.absent(), + this.lensModel = const Optional.absent(), + this.libraryId = const Optional.absent(), + this.make = const Optional.absent(), + this.model = const Optional.absent(), + this.ocr = const Optional.absent(), + this.order = const Optional.absent(), + this.originalFileName = const Optional.absent(), + this.originalPath = const Optional.absent(), + this.page = const Optional.absent(), + this.personIds = const Optional.present(const []), + this.previewPath = const Optional.absent(), + this.rating = const Optional.absent(), + this.size = const Optional.absent(), + this.state = const Optional.absent(), + this.tagIds = const Optional.present(const []), + this.takenAfter = const Optional.absent(), + this.takenBefore = const Optional.absent(), + this.thumbnailPath = const Optional.absent(), + this.trashedAfter = const Optional.absent(), + this.trashedBefore = const Optional.absent(), + this.type = const Optional.absent(), + this.updatedAfter = const Optional.absent(), + this.updatedBefore = const Optional.absent(), + this.visibility = const Optional.absent(), + this.withDeleted = const Optional.absent(), + this.withExif = const Optional.absent(), + this.withPeople = const Optional.absent(), + this.withStacked = const Optional.absent(), }); /// Filter by album IDs - List albumIds; + Optional?> albumIds; /// Filter by file checksum /// @@ -67,13 +67,13 @@ class MetadataSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? checksum; + Optional checksum; /// Filter by city name - String? city; + Optional city; /// Filter by country name - String? country; + Optional country; /// Filter by creation date (after) /// @@ -82,7 +82,7 @@ class MetadataSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - DateTime? createdAfter; + Optional createdAfter; /// Filter by creation date (before) /// @@ -91,7 +91,7 @@ class MetadataSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - DateTime? createdBefore; + Optional createdBefore; /// Filter by description text /// @@ -100,7 +100,7 @@ class MetadataSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? description; + Optional description; /// Filter by encoded video file path /// @@ -109,7 +109,7 @@ class MetadataSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? encodedVideoPath; + Optional encodedVideoPath; /// Filter by asset ID /// @@ -118,7 +118,7 @@ class MetadataSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? id; + Optional id; /// Filter by encoded status /// @@ -127,7 +127,7 @@ class MetadataSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? isEncoded; + Optional isEncoded; /// Filter by favorite status /// @@ -136,7 +136,7 @@ class MetadataSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? isFavorite; + Optional isFavorite; /// Filter by motion photo status /// @@ -145,7 +145,7 @@ class MetadataSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? isMotion; + Optional isMotion; /// Filter assets not in any album /// @@ -154,7 +154,7 @@ class MetadataSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? isNotInAlbum; + Optional isNotInAlbum; /// Filter by offline status /// @@ -163,19 +163,19 @@ class MetadataSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? isOffline; + Optional isOffline; /// Filter by lens model - String? lensModel; + Optional lensModel; /// Library ID to filter by - String? libraryId; + Optional libraryId; /// Filter by camera make - String? make; + Optional make; /// Filter by camera model - String? model; + Optional model; /// Filter by OCR text content /// @@ -184,7 +184,7 @@ class MetadataSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? ocr; + Optional ocr; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -192,7 +192,7 @@ class MetadataSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - AssetOrder? order; + Optional order; /// Filter by original file name /// @@ -201,7 +201,7 @@ class MetadataSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? originalFileName; + Optional originalFileName; /// Filter by original file path /// @@ -210,7 +210,7 @@ class MetadataSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? originalPath; + Optional originalPath; /// Page number /// @@ -222,10 +222,10 @@ class MetadataSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - int? page; + Optional page; /// Filter by person IDs - List personIds; + Optional?> personIds; /// Filter by preview file path /// @@ -234,13 +234,13 @@ class MetadataSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? previewPath; + Optional previewPath; /// Filter by rating [1-5], or null for unrated /// /// Minimum value: -1 /// Maximum value: 5 - int? rating; + Optional rating; /// Number of results to return /// @@ -252,13 +252,13 @@ class MetadataSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - int? size; + Optional size; /// Filter by state/province name - String? state; + Optional state; /// Filter by tag IDs - List? tagIds; + Optional?> tagIds; /// Filter by taken date (after) /// @@ -267,7 +267,7 @@ class MetadataSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - DateTime? takenAfter; + Optional takenAfter; /// Filter by taken date (before) /// @@ -276,7 +276,7 @@ class MetadataSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - DateTime? takenBefore; + Optional takenBefore; /// Filter by thumbnail file path /// @@ -285,7 +285,7 @@ class MetadataSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? thumbnailPath; + Optional thumbnailPath; /// Filter by trash date (after) /// @@ -294,7 +294,7 @@ class MetadataSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - DateTime? trashedAfter; + Optional trashedAfter; /// Filter by trash date (before) /// @@ -303,7 +303,7 @@ class MetadataSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - DateTime? trashedBefore; + Optional trashedBefore; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -311,7 +311,7 @@ class MetadataSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - AssetTypeEnum? type; + Optional type; /// Filter by update date (after) /// @@ -320,7 +320,7 @@ class MetadataSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - DateTime? updatedAfter; + Optional updatedAfter; /// Filter by update date (before) /// @@ -329,7 +329,7 @@ class MetadataSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - DateTime? updatedBefore; + Optional updatedBefore; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -337,7 +337,7 @@ class MetadataSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - AssetVisibility? visibility; + Optional visibility; /// Include deleted assets /// @@ -346,7 +346,7 @@ class MetadataSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? withDeleted; + Optional withDeleted; /// Include EXIF data in response /// @@ -355,7 +355,7 @@ class MetadataSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? withExif; + Optional withExif; /// Include people data in response /// @@ -364,7 +364,7 @@ class MetadataSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? withPeople; + Optional withPeople; /// Include stacked assets /// @@ -373,7 +373,7 @@ class MetadataSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? withStacked; + Optional withStacked; @override bool operator ==(Object other) => identical(this, other) || other is MetadataSearchDto && @@ -471,223 +471,189 @@ class MetadataSearchDto { Map toJson() { final json = {}; - json[r'albumIds'] = this.albumIds; - if (this.checksum != null) { - json[r'checksum'] = this.checksum; - } else { - // json[r'checksum'] = null; + if (this.albumIds.isPresent) { + final value = this.albumIds.value; + json[r'albumIds'] = value; } - if (this.city != null) { - json[r'city'] = this.city; - } else { - // json[r'city'] = null; + if (this.checksum.isPresent) { + final value = this.checksum.value; + json[r'checksum'] = value; } - if (this.country != null) { - json[r'country'] = this.country; - } else { - // json[r'country'] = null; + if (this.city.isPresent) { + final value = this.city.value; + json[r'city'] = value; } - if (this.createdAfter != null) { - json[r'createdAfter'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.createdAfter!.millisecondsSinceEpoch - : this.createdAfter!.toUtc().toIso8601String(); - } else { - // json[r'createdAfter'] = null; + if (this.country.isPresent) { + final value = this.country.value; + json[r'country'] = value; } - if (this.createdBefore != null) { - json[r'createdBefore'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.createdBefore!.millisecondsSinceEpoch - : this.createdBefore!.toUtc().toIso8601String(); - } else { - // json[r'createdBefore'] = null; + if (this.createdAfter.isPresent) { + final value = this.createdAfter.value; + json[r'createdAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.description != null) { - json[r'description'] = this.description; - } else { - // json[r'description'] = null; + if (this.createdBefore.isPresent) { + final value = this.createdBefore.value; + json[r'createdBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.encodedVideoPath != null) { - json[r'encodedVideoPath'] = this.encodedVideoPath; - } else { - // json[r'encodedVideoPath'] = null; + if (this.description.isPresent) { + final value = this.description.value; + json[r'description'] = value; } - if (this.id != null) { - json[r'id'] = this.id; - } else { - // json[r'id'] = null; + if (this.encodedVideoPath.isPresent) { + final value = this.encodedVideoPath.value; + json[r'encodedVideoPath'] = value; } - if (this.isEncoded != null) { - json[r'isEncoded'] = this.isEncoded; - } else { - // json[r'isEncoded'] = null; + if (this.id.isPresent) { + final value = this.id.value; + json[r'id'] = value; } - if (this.isFavorite != null) { - json[r'isFavorite'] = this.isFavorite; - } else { - // json[r'isFavorite'] = null; + if (this.isEncoded.isPresent) { + final value = this.isEncoded.value; + json[r'isEncoded'] = value; } - if (this.isMotion != null) { - json[r'isMotion'] = this.isMotion; - } else { - // json[r'isMotion'] = null; + if (this.isFavorite.isPresent) { + final value = this.isFavorite.value; + json[r'isFavorite'] = value; } - if (this.isNotInAlbum != null) { - json[r'isNotInAlbum'] = this.isNotInAlbum; - } else { - // json[r'isNotInAlbum'] = null; + if (this.isMotion.isPresent) { + final value = this.isMotion.value; + json[r'isMotion'] = value; } - if (this.isOffline != null) { - json[r'isOffline'] = this.isOffline; - } else { - // json[r'isOffline'] = null; + if (this.isNotInAlbum.isPresent) { + final value = this.isNotInAlbum.value; + json[r'isNotInAlbum'] = value; } - if (this.lensModel != null) { - json[r'lensModel'] = this.lensModel; - } else { - // json[r'lensModel'] = null; + if (this.isOffline.isPresent) { + final value = this.isOffline.value; + json[r'isOffline'] = value; } - if (this.libraryId != null) { - json[r'libraryId'] = this.libraryId; - } else { - // json[r'libraryId'] = null; + if (this.lensModel.isPresent) { + final value = this.lensModel.value; + json[r'lensModel'] = value; } - if (this.make != null) { - json[r'make'] = this.make; - } else { - // json[r'make'] = null; + if (this.libraryId.isPresent) { + final value = this.libraryId.value; + json[r'libraryId'] = value; } - if (this.model != null) { - json[r'model'] = this.model; - } else { - // json[r'model'] = null; + if (this.make.isPresent) { + final value = this.make.value; + json[r'make'] = value; } - if (this.ocr != null) { - json[r'ocr'] = this.ocr; - } else { - // json[r'ocr'] = null; + if (this.model.isPresent) { + final value = this.model.value; + json[r'model'] = value; } - if (this.order != null) { - json[r'order'] = this.order; - } else { - // json[r'order'] = null; + if (this.ocr.isPresent) { + final value = this.ocr.value; + json[r'ocr'] = value; } - if (this.originalFileName != null) { - json[r'originalFileName'] = this.originalFileName; - } else { - // json[r'originalFileName'] = null; + if (this.order.isPresent) { + final value = this.order.value; + json[r'order'] = value; } - if (this.originalPath != null) { - json[r'originalPath'] = this.originalPath; - } else { - // json[r'originalPath'] = null; + if (this.originalFileName.isPresent) { + final value = this.originalFileName.value; + json[r'originalFileName'] = value; } - if (this.page != null) { - json[r'page'] = this.page; - } else { - // json[r'page'] = null; + if (this.originalPath.isPresent) { + final value = this.originalPath.value; + json[r'originalPath'] = value; } - json[r'personIds'] = this.personIds; - if (this.previewPath != null) { - json[r'previewPath'] = this.previewPath; - } else { - // json[r'previewPath'] = null; + if (this.page.isPresent) { + final value = this.page.value; + json[r'page'] = value; } - if (this.rating != null) { - json[r'rating'] = this.rating; - } else { - // json[r'rating'] = null; + if (this.personIds.isPresent) { + final value = this.personIds.value; + json[r'personIds'] = value; } - if (this.size != null) { - json[r'size'] = this.size; - } else { - // json[r'size'] = null; + if (this.previewPath.isPresent) { + final value = this.previewPath.value; + json[r'previewPath'] = value; } - if (this.state != null) { - json[r'state'] = this.state; - } else { - // json[r'state'] = null; + if (this.rating.isPresent) { + final value = this.rating.value; + json[r'rating'] = value; } - if (this.tagIds != null) { - json[r'tagIds'] = this.tagIds; - } else { - // json[r'tagIds'] = null; + if (this.size.isPresent) { + final value = this.size.value; + json[r'size'] = value; } - if (this.takenAfter != null) { - json[r'takenAfter'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.takenAfter!.millisecondsSinceEpoch - : this.takenAfter!.toUtc().toIso8601String(); - } else { - // json[r'takenAfter'] = null; + if (this.state.isPresent) { + final value = this.state.value; + json[r'state'] = value; } - if (this.takenBefore != null) { - json[r'takenBefore'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.takenBefore!.millisecondsSinceEpoch - : this.takenBefore!.toUtc().toIso8601String(); - } else { - // json[r'takenBefore'] = null; + if (this.tagIds.isPresent) { + final value = this.tagIds.value; + json[r'tagIds'] = value; } - if (this.thumbnailPath != null) { - json[r'thumbnailPath'] = this.thumbnailPath; - } else { - // json[r'thumbnailPath'] = null; + if (this.takenAfter.isPresent) { + final value = this.takenAfter.value; + json[r'takenAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.trashedAfter != null) { - json[r'trashedAfter'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.trashedAfter!.millisecondsSinceEpoch - : this.trashedAfter!.toUtc().toIso8601String(); - } else { - // json[r'trashedAfter'] = null; + if (this.takenBefore.isPresent) { + final value = this.takenBefore.value; + json[r'takenBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.trashedBefore != null) { - json[r'trashedBefore'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.trashedBefore!.millisecondsSinceEpoch - : this.trashedBefore!.toUtc().toIso8601String(); - } else { - // json[r'trashedBefore'] = null; + if (this.thumbnailPath.isPresent) { + final value = this.thumbnailPath.value; + json[r'thumbnailPath'] = value; } - if (this.type != null) { - json[r'type'] = this.type; - } else { - // json[r'type'] = null; + if (this.trashedAfter.isPresent) { + final value = this.trashedAfter.value; + json[r'trashedAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.updatedAfter != null) { - json[r'updatedAfter'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.updatedAfter!.millisecondsSinceEpoch - : this.updatedAfter!.toUtc().toIso8601String(); - } else { - // json[r'updatedAfter'] = null; + if (this.trashedBefore.isPresent) { + final value = this.trashedBefore.value; + json[r'trashedBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.updatedBefore != null) { - json[r'updatedBefore'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.updatedBefore!.millisecondsSinceEpoch - : this.updatedBefore!.toUtc().toIso8601String(); - } else { - // json[r'updatedBefore'] = null; + if (this.type.isPresent) { + final value = this.type.value; + json[r'type'] = value; } - if (this.visibility != null) { - json[r'visibility'] = this.visibility; - } else { - // json[r'visibility'] = null; + if (this.updatedAfter.isPresent) { + final value = this.updatedAfter.value; + json[r'updatedAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.withDeleted != null) { - json[r'withDeleted'] = this.withDeleted; - } else { - // json[r'withDeleted'] = null; + if (this.updatedBefore.isPresent) { + final value = this.updatedBefore.value; + json[r'updatedBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.withExif != null) { - json[r'withExif'] = this.withExif; - } else { - // json[r'withExif'] = null; + if (this.visibility.isPresent) { + final value = this.visibility.value; + json[r'visibility'] = value; } - if (this.withPeople != null) { - json[r'withPeople'] = this.withPeople; - } else { - // json[r'withPeople'] = null; + if (this.withDeleted.isPresent) { + final value = this.withDeleted.value; + json[r'withDeleted'] = value; } - if (this.withStacked != null) { - json[r'withStacked'] = this.withStacked; - } else { - // json[r'withStacked'] = null; + if (this.withExif.isPresent) { + final value = this.withExif.value; + json[r'withExif'] = value; + } + if (this.withPeople.isPresent) { + final value = this.withPeople.value; + json[r'withPeople'] = value; + } + if (this.withStacked.isPresent) { + final value = this.withStacked.value; + json[r'withStacked'] = value; } return json; } @@ -701,54 +667,54 @@ class MetadataSearchDto { final json = value.cast(); return MetadataSearchDto( - albumIds: json[r'albumIds'] is Iterable + albumIds: json.containsKey(r'albumIds') ? Optional.present(json[r'albumIds'] is Iterable ? (json[r'albumIds'] as Iterable).cast().toList(growable: false) - : const [], - checksum: mapValueOfType(json, r'checksum'), - city: mapValueOfType(json, r'city'), - country: mapValueOfType(json, r'country'), - createdAfter: mapDateTime(json, r'createdAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - createdBefore: mapDateTime(json, r'createdBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - description: mapValueOfType(json, r'description'), - encodedVideoPath: mapValueOfType(json, r'encodedVideoPath'), - id: mapValueOfType(json, r'id'), - isEncoded: mapValueOfType(json, r'isEncoded'), - isFavorite: mapValueOfType(json, r'isFavorite'), - isMotion: mapValueOfType(json, r'isMotion'), - isNotInAlbum: mapValueOfType(json, r'isNotInAlbum'), - isOffline: mapValueOfType(json, r'isOffline'), - lensModel: mapValueOfType(json, r'lensModel'), - libraryId: mapValueOfType(json, r'libraryId'), - make: mapValueOfType(json, r'make'), - model: mapValueOfType(json, r'model'), - ocr: mapValueOfType(json, r'ocr'), - order: AssetOrder.fromJson(json[r'order']), - originalFileName: mapValueOfType(json, r'originalFileName'), - originalPath: mapValueOfType(json, r'originalPath'), - page: mapValueOfType(json, r'page'), - personIds: json[r'personIds'] is Iterable + : const []) : const Optional.absent(), + checksum: json.containsKey(r'checksum') ? Optional.present(mapValueOfType(json, r'checksum')) : const Optional.absent(), + city: json.containsKey(r'city') ? Optional.present(mapValueOfType(json, r'city')) : const Optional.absent(), + country: json.containsKey(r'country') ? Optional.present(mapValueOfType(json, r'country')) : const Optional.absent(), + createdAfter: json.containsKey(r'createdAfter') ? Optional.present(mapDateTime(json, r'createdAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + createdBefore: json.containsKey(r'createdBefore') ? Optional.present(mapDateTime(json, r'createdBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + description: json.containsKey(r'description') ? Optional.present(mapValueOfType(json, r'description')) : const Optional.absent(), + encodedVideoPath: json.containsKey(r'encodedVideoPath') ? Optional.present(mapValueOfType(json, r'encodedVideoPath')) : const Optional.absent(), + id: json.containsKey(r'id') ? Optional.present(mapValueOfType(json, r'id')) : const Optional.absent(), + isEncoded: json.containsKey(r'isEncoded') ? Optional.present(mapValueOfType(json, r'isEncoded')) : const Optional.absent(), + isFavorite: json.containsKey(r'isFavorite') ? Optional.present(mapValueOfType(json, r'isFavorite')) : const Optional.absent(), + isMotion: json.containsKey(r'isMotion') ? Optional.present(mapValueOfType(json, r'isMotion')) : const Optional.absent(), + isNotInAlbum: json.containsKey(r'isNotInAlbum') ? Optional.present(mapValueOfType(json, r'isNotInAlbum')) : const Optional.absent(), + isOffline: json.containsKey(r'isOffline') ? Optional.present(mapValueOfType(json, r'isOffline')) : const Optional.absent(), + lensModel: json.containsKey(r'lensModel') ? Optional.present(mapValueOfType(json, r'lensModel')) : const Optional.absent(), + libraryId: json.containsKey(r'libraryId') ? Optional.present(mapValueOfType(json, r'libraryId')) : const Optional.absent(), + make: json.containsKey(r'make') ? Optional.present(mapValueOfType(json, r'make')) : const Optional.absent(), + model: json.containsKey(r'model') ? Optional.present(mapValueOfType(json, r'model')) : const Optional.absent(), + ocr: json.containsKey(r'ocr') ? Optional.present(mapValueOfType(json, r'ocr')) : const Optional.absent(), + order: json.containsKey(r'order') ? Optional.present(AssetOrder.fromJson(json[r'order'])) : const Optional.absent(), + originalFileName: json.containsKey(r'originalFileName') ? Optional.present(mapValueOfType(json, r'originalFileName')) : const Optional.absent(), + originalPath: json.containsKey(r'originalPath') ? Optional.present(mapValueOfType(json, r'originalPath')) : const Optional.absent(), + page: json.containsKey(r'page') ? Optional.present(json[r'page'] == null ? null : int.parse('${json[r'page']}')) : const Optional.absent(), + personIds: json.containsKey(r'personIds') ? Optional.present(json[r'personIds'] is Iterable ? (json[r'personIds'] as Iterable).cast().toList(growable: false) - : const [], - previewPath: mapValueOfType(json, r'previewPath'), - rating: mapValueOfType(json, r'rating'), - size: mapValueOfType(json, r'size'), - state: mapValueOfType(json, r'state'), - tagIds: json[r'tagIds'] is Iterable + : const []) : const Optional.absent(), + previewPath: json.containsKey(r'previewPath') ? Optional.present(mapValueOfType(json, r'previewPath')) : const Optional.absent(), + rating: json.containsKey(r'rating') ? Optional.present(json[r'rating'] == null ? null : int.parse('${json[r'rating']}')) : const Optional.absent(), + size: json.containsKey(r'size') ? Optional.present(json[r'size'] == null ? null : int.parse('${json[r'size']}')) : const Optional.absent(), + state: json.containsKey(r'state') ? Optional.present(mapValueOfType(json, r'state')) : const Optional.absent(), + tagIds: json.containsKey(r'tagIds') ? Optional.present(json[r'tagIds'] is Iterable ? (json[r'tagIds'] as Iterable).cast().toList(growable: false) - : const [], - takenAfter: mapDateTime(json, r'takenAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - takenBefore: mapDateTime(json, r'takenBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - thumbnailPath: mapValueOfType(json, r'thumbnailPath'), - trashedAfter: mapDateTime(json, r'trashedAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - trashedBefore: mapDateTime(json, r'trashedBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - type: AssetTypeEnum.fromJson(json[r'type']), - updatedAfter: mapDateTime(json, r'updatedAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - updatedBefore: mapDateTime(json, r'updatedBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - visibility: AssetVisibility.fromJson(json[r'visibility']), - withDeleted: mapValueOfType(json, r'withDeleted'), - withExif: mapValueOfType(json, r'withExif'), - withPeople: mapValueOfType(json, r'withPeople'), - withStacked: mapValueOfType(json, r'withStacked'), + : const []) : const Optional.absent(), + takenAfter: json.containsKey(r'takenAfter') ? Optional.present(mapDateTime(json, r'takenAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + takenBefore: json.containsKey(r'takenBefore') ? Optional.present(mapDateTime(json, r'takenBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + thumbnailPath: json.containsKey(r'thumbnailPath') ? Optional.present(mapValueOfType(json, r'thumbnailPath')) : const Optional.absent(), + trashedAfter: json.containsKey(r'trashedAfter') ? Optional.present(mapDateTime(json, r'trashedAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + trashedBefore: json.containsKey(r'trashedBefore') ? Optional.present(mapDateTime(json, r'trashedBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + type: json.containsKey(r'type') ? Optional.present(AssetTypeEnum.fromJson(json[r'type'])) : const Optional.absent(), + updatedAfter: json.containsKey(r'updatedAfter') ? Optional.present(mapDateTime(json, r'updatedAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + updatedBefore: json.containsKey(r'updatedBefore') ? Optional.present(mapDateTime(json, r'updatedBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + visibility: json.containsKey(r'visibility') ? Optional.present(AssetVisibility.fromJson(json[r'visibility'])) : const Optional.absent(), + withDeleted: json.containsKey(r'withDeleted') ? Optional.present(mapValueOfType(json, r'withDeleted')) : const Optional.absent(), + withExif: json.containsKey(r'withExif') ? Optional.present(mapValueOfType(json, r'withExif')) : const Optional.absent(), + withPeople: json.containsKey(r'withPeople') ? Optional.present(mapValueOfType(json, r'withPeople')) : const Optional.absent(), + withStacked: json.containsKey(r'withStacked') ? Optional.present(mapValueOfType(json, r'withStacked')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/notification_create_dto.dart b/mobile/openapi/lib/model/notification_create_dto.dart index f9771246f9..c6e7c02231 100644 --- a/mobile/openapi/lib/model/notification_create_dto.dart +++ b/mobile/openapi/lib/model/notification_create_dto.dart @@ -13,20 +13,20 @@ part of openapi.api; class NotificationCreateDto { /// Returns a new [NotificationCreateDto] instance. NotificationCreateDto({ - this.data = const {}, - this.description, - this.level, - this.readAt, + this.data = const Optional.present(const {}), + this.description = const Optional.absent(), + this.level = const Optional.absent(), + this.readAt = const Optional.absent(), required this.title, - this.type, + this.type = const Optional.absent(), required this.userId, }); /// Additional notification data - Map data; + Optional?> data; /// Notification description - String? description; + Optional description; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -34,10 +34,10 @@ class NotificationCreateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - NotificationLevel? level; + Optional level; /// Date when notification was read - DateTime? readAt; + Optional readAt; /// Notification title String title; @@ -48,7 +48,7 @@ class NotificationCreateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - NotificationType? type; + Optional type; /// User ID to send notification to String userId; @@ -79,29 +79,28 @@ class NotificationCreateDto { Map toJson() { final json = {}; - json[r'data'] = this.data; - if (this.description != null) { - json[r'description'] = this.description; - } else { - // json[r'description'] = null; + if (this.data.isPresent) { + final value = this.data.value; + json[r'data'] = value; } - if (this.level != null) { - json[r'level'] = this.level; - } else { - // json[r'level'] = null; + if (this.description.isPresent) { + final value = this.description.value; + json[r'description'] = value; } - if (this.readAt != null) { - json[r'readAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.readAt!.millisecondsSinceEpoch - : this.readAt!.toUtc().toIso8601String(); - } else { - // json[r'readAt'] = null; + if (this.level.isPresent) { + final value = this.level.value; + json[r'level'] = value; + } + if (this.readAt.isPresent) { + final value = this.readAt.value; + json[r'readAt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } json[r'title'] = this.title; - if (this.type != null) { - json[r'type'] = this.type; - } else { - // json[r'type'] = null; + if (this.type.isPresent) { + final value = this.type.value; + json[r'type'] = value; } json[r'userId'] = this.userId; return json; @@ -116,12 +115,12 @@ class NotificationCreateDto { final json = value.cast(); return NotificationCreateDto( - data: mapCastOfType(json, r'data') ?? const {}, - description: mapValueOfType(json, r'description'), - level: NotificationLevel.fromJson(json[r'level']), - readAt: mapDateTime(json, r'readAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), + data: json.containsKey(r'data') ? Optional.present(mapCastOfType(json, r'data')) : const Optional.absent(), + description: json.containsKey(r'description') ? Optional.present(mapValueOfType(json, r'description')) : const Optional.absent(), + level: json.containsKey(r'level') ? Optional.present(NotificationLevel.fromJson(json[r'level'])) : const Optional.absent(), + readAt: json.containsKey(r'readAt') ? Optional.present(mapDateTime(json, r'readAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), title: mapValueOfType(json, r'title')!, - type: NotificationType.fromJson(json[r'type']), + type: json.containsKey(r'type') ? Optional.present(NotificationType.fromJson(json[r'type'])) : const Optional.absent(), userId: mapValueOfType(json, r'userId')!, ); } diff --git a/mobile/openapi/lib/model/notification_dto.dart b/mobile/openapi/lib/model/notification_dto.dart index ad0e79cb27..4e4fab8ee6 100644 --- a/mobile/openapi/lib/model/notification_dto.dart +++ b/mobile/openapi/lib/model/notification_dto.dart @@ -14,11 +14,11 @@ class NotificationDto { /// Returns a new [NotificationDto] instance. NotificationDto({ required this.createdAt, - this.data = const {}, - this.description, + this.data = const Optional.present(const {}), + this.description = const Optional.absent(), required this.id, required this.level, - this.readAt, + this.readAt = const Optional.absent(), required this.title, required this.type, }); @@ -27,7 +27,7 @@ class NotificationDto { DateTime createdAt; /// Additional notification data - Map data; + Optional?> data; /// Notification description /// @@ -36,7 +36,7 @@ class NotificationDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? description; + Optional description; /// Notification ID String id; @@ -50,7 +50,7 @@ class NotificationDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - DateTime? readAt; + Optional readAt; /// Notification title String title; @@ -88,20 +88,21 @@ class NotificationDto { json[r'createdAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') ? this.createdAt.millisecondsSinceEpoch : this.createdAt.toUtc().toIso8601String(); - json[r'data'] = this.data; - if (this.description != null) { - json[r'description'] = this.description; - } else { - // json[r'description'] = null; + if (this.data.isPresent) { + final value = this.data.value; + json[r'data'] = value; + } + if (this.description.isPresent) { + final value = this.description.value; + json[r'description'] = value; } json[r'id'] = this.id; json[r'level'] = this.level; - if (this.readAt != null) { - json[r'readAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.readAt!.millisecondsSinceEpoch - : this.readAt!.toUtc().toIso8601String(); - } else { - // json[r'readAt'] = null; + if (this.readAt.isPresent) { + final value = this.readAt.value; + json[r'readAt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } json[r'title'] = this.title; json[r'type'] = this.type; @@ -118,11 +119,11 @@ class NotificationDto { return NotificationDto( createdAt: mapDateTime(json, r'createdAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')!, - data: mapCastOfType(json, r'data') ?? const {}, - description: mapValueOfType(json, r'description'), + data: json.containsKey(r'data') ? Optional.present(mapCastOfType(json, r'data')) : const Optional.absent(), + description: json.containsKey(r'description') ? Optional.present(mapValueOfType(json, r'description')) : const Optional.absent(), id: mapValueOfType(json, r'id')!, level: NotificationLevel.fromJson(json[r'level'])!, - readAt: mapDateTime(json, r'readAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), + readAt: json.containsKey(r'readAt') ? Optional.present(mapDateTime(json, r'readAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), title: mapValueOfType(json, r'title')!, type: NotificationType.fromJson(json[r'type'])!, ); diff --git a/mobile/openapi/lib/model/notification_update_all_dto.dart b/mobile/openapi/lib/model/notification_update_all_dto.dart index 5ac61ededc..1a95e2647a 100644 --- a/mobile/openapi/lib/model/notification_update_all_dto.dart +++ b/mobile/openapi/lib/model/notification_update_all_dto.dart @@ -14,14 +14,14 @@ class NotificationUpdateAllDto { /// Returns a new [NotificationUpdateAllDto] instance. NotificationUpdateAllDto({ this.ids = const [], - this.readAt, + this.readAt = const Optional.absent(), }); /// Notification IDs to update List ids; /// Date when notifications were read - DateTime? readAt; + Optional readAt; @override bool operator ==(Object other) => identical(this, other) || other is NotificationUpdateAllDto && @@ -40,12 +40,11 @@ class NotificationUpdateAllDto { Map toJson() { final json = {}; json[r'ids'] = this.ids; - if (this.readAt != null) { - json[r'readAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.readAt!.millisecondsSinceEpoch - : this.readAt!.toUtc().toIso8601String(); - } else { - // json[r'readAt'] = null; + if (this.readAt.isPresent) { + final value = this.readAt.value; + json[r'readAt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } return json; } @@ -62,7 +61,7 @@ class NotificationUpdateAllDto { ids: json[r'ids'] is Iterable ? (json[r'ids'] as Iterable).cast().toList(growable: false) : const [], - readAt: mapDateTime(json, r'readAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), + readAt: json.containsKey(r'readAt') ? Optional.present(mapDateTime(json, r'readAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/notification_update_dto.dart b/mobile/openapi/lib/model/notification_update_dto.dart index c5d949d7b2..4fa0cd5e97 100644 --- a/mobile/openapi/lib/model/notification_update_dto.dart +++ b/mobile/openapi/lib/model/notification_update_dto.dart @@ -13,11 +13,11 @@ part of openapi.api; class NotificationUpdateDto { /// Returns a new [NotificationUpdateDto] instance. NotificationUpdateDto({ - this.readAt, + this.readAt = const Optional.absent(), }); /// Date when notification was read - DateTime? readAt; + Optional readAt; @override bool operator ==(Object other) => identical(this, other) || other is NotificationUpdateDto && @@ -33,12 +33,11 @@ class NotificationUpdateDto { Map toJson() { final json = {}; - if (this.readAt != null) { - json[r'readAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.readAt!.millisecondsSinceEpoch - : this.readAt!.toUtc().toIso8601String(); - } else { - // json[r'readAt'] = null; + if (this.readAt.isPresent) { + final value = this.readAt.value; + json[r'readAt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } return json; } @@ -52,7 +51,7 @@ class NotificationUpdateDto { final json = value.cast(); return NotificationUpdateDto( - readAt: mapDateTime(json, r'readAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), + readAt: json.containsKey(r'readAt') ? Optional.present(mapDateTime(json, r'readAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/o_auth_callback_dto.dart b/mobile/openapi/lib/model/o_auth_callback_dto.dart index d94374935a..61de33e1a6 100644 --- a/mobile/openapi/lib/model/o_auth_callback_dto.dart +++ b/mobile/openapi/lib/model/o_auth_callback_dto.dart @@ -13,8 +13,8 @@ part of openapi.api; class OAuthCallbackDto { /// Returns a new [OAuthCallbackDto] instance. OAuthCallbackDto({ - this.codeVerifier, - this.state, + this.codeVerifier = const Optional.absent(), + this.state = const Optional.absent(), required this.url, }); @@ -25,7 +25,7 @@ class OAuthCallbackDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? codeVerifier; + Optional codeVerifier; /// OAuth state parameter /// @@ -34,7 +34,7 @@ class OAuthCallbackDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? state; + Optional state; /// OAuth callback URL String url; @@ -57,15 +57,13 @@ class OAuthCallbackDto { Map toJson() { final json = {}; - if (this.codeVerifier != null) { - json[r'codeVerifier'] = this.codeVerifier; - } else { - // json[r'codeVerifier'] = null; + if (this.codeVerifier.isPresent) { + final value = this.codeVerifier.value; + json[r'codeVerifier'] = value; } - if (this.state != null) { - json[r'state'] = this.state; - } else { - // json[r'state'] = null; + if (this.state.isPresent) { + final value = this.state.value; + json[r'state'] = value; } json[r'url'] = this.url; return json; @@ -80,8 +78,8 @@ class OAuthCallbackDto { final json = value.cast(); return OAuthCallbackDto( - codeVerifier: mapValueOfType(json, r'codeVerifier'), - state: mapValueOfType(json, r'state'), + codeVerifier: json.containsKey(r'codeVerifier') ? Optional.present(mapValueOfType(json, r'codeVerifier')) : const Optional.absent(), + state: json.containsKey(r'state') ? Optional.present(mapValueOfType(json, r'state')) : const Optional.absent(), url: mapValueOfType(json, r'url')!, ); } diff --git a/mobile/openapi/lib/model/o_auth_config_dto.dart b/mobile/openapi/lib/model/o_auth_config_dto.dart index 1c9ce8d5b8..fb9f95dd92 100644 --- a/mobile/openapi/lib/model/o_auth_config_dto.dart +++ b/mobile/openapi/lib/model/o_auth_config_dto.dart @@ -13,9 +13,9 @@ part of openapi.api; class OAuthConfigDto { /// Returns a new [OAuthConfigDto] instance. OAuthConfigDto({ - this.codeChallenge, + this.codeChallenge = const Optional.absent(), required this.redirectUri, - this.state, + this.state = const Optional.absent(), }); /// OAuth code challenge (PKCE) @@ -25,7 +25,7 @@ class OAuthConfigDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? codeChallenge; + Optional codeChallenge; /// OAuth redirect URI String redirectUri; @@ -37,7 +37,7 @@ class OAuthConfigDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? state; + Optional state; @override bool operator ==(Object other) => identical(this, other) || other is OAuthConfigDto && @@ -57,16 +57,14 @@ class OAuthConfigDto { Map toJson() { final json = {}; - if (this.codeChallenge != null) { - json[r'codeChallenge'] = this.codeChallenge; - } else { - // json[r'codeChallenge'] = null; + if (this.codeChallenge.isPresent) { + final value = this.codeChallenge.value; + json[r'codeChallenge'] = value; } json[r'redirectUri'] = this.redirectUri; - if (this.state != null) { - json[r'state'] = this.state; - } else { - // json[r'state'] = null; + if (this.state.isPresent) { + final value = this.state.value; + json[r'state'] = value; } return json; } @@ -80,9 +78,9 @@ class OAuthConfigDto { final json = value.cast(); return OAuthConfigDto( - codeChallenge: mapValueOfType(json, r'codeChallenge'), + codeChallenge: json.containsKey(r'codeChallenge') ? Optional.present(mapValueOfType(json, r'codeChallenge')) : const Optional.absent(), redirectUri: mapValueOfType(json, r'redirectUri')!, - state: mapValueOfType(json, r'state'), + state: json.containsKey(r'state') ? Optional.present(mapValueOfType(json, r'state')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/o_auth_token_endpoint_auth_method.dart b/mobile/openapi/lib/model/o_auth_token_endpoint_auth_method.dart index b63f027af7..414108068a 100644 --- a/mobile/openapi/lib/model/o_auth_token_endpoint_auth_method.dart +++ b/mobile/openapi/lib/model/o_auth_token_endpoint_auth_method.dart @@ -23,13 +23,13 @@ class OAuthTokenEndpointAuthMethod { String toJson() => value; - static const post = OAuthTokenEndpointAuthMethod._(r'client_secret_post'); - static const basic = OAuthTokenEndpointAuthMethod._(r'client_secret_basic'); + static const clientSecretPost = OAuthTokenEndpointAuthMethod._(r'client_secret_post'); + static const clientSecretBasic = OAuthTokenEndpointAuthMethod._(r'client_secret_basic'); /// List of all possible values in this [enum][OAuthTokenEndpointAuthMethod]. static const values = [ - post, - basic, + clientSecretPost, + clientSecretBasic, ]; static OAuthTokenEndpointAuthMethod? fromJson(dynamic value) => OAuthTokenEndpointAuthMethodTypeTransformer().decode(value); @@ -68,8 +68,8 @@ class OAuthTokenEndpointAuthMethodTypeTransformer { OAuthTokenEndpointAuthMethod? decode(dynamic data, {bool allowNull = true}) { if (data != null) { switch (data) { - case r'client_secret_post': return OAuthTokenEndpointAuthMethod.post; - case r'client_secret_basic': return OAuthTokenEndpointAuthMethod.basic; + case r'client_secret_post': return OAuthTokenEndpointAuthMethod.clientSecretPost; + case r'client_secret_basic': return OAuthTokenEndpointAuthMethod.clientSecretBasic; default: if (!allowNull) { throw ArgumentError('Unknown enum value to decode: $data'); diff --git a/mobile/openapi/lib/model/ocr_config.dart b/mobile/openapi/lib/model/ocr_config.dart index 2ce5646731..d58c8af3ee 100644 --- a/mobile/openapi/lib/model/ocr_config.dart +++ b/mobile/openapi/lib/model/ocr_config.dart @@ -85,8 +85,8 @@ class OcrConfig { return OcrConfig( enabled: mapValueOfType(json, r'enabled')!, maxResolution: mapValueOfType(json, r'maxResolution')!, - minDetectionScore: (mapValueOfType(json, r'minDetectionScore')!).toDouble(), - minRecognitionScore: (mapValueOfType(json, r'minRecognitionScore')!).toDouble(), + minDetectionScore: mapValueOfType(json, r'minDetectionScore')!, + minRecognitionScore: mapValueOfType(json, r'minRecognitionScore')!, modelName: mapValueOfType(json, r'modelName')!, ); } diff --git a/mobile/openapi/lib/model/partner_direction.dart b/mobile/openapi/lib/model/partner_direction.dart index c5e3b308ac..c1e38b8dfe 100644 --- a/mobile/openapi/lib/model/partner_direction.dart +++ b/mobile/openapi/lib/model/partner_direction.dart @@ -23,13 +23,13 @@ class PartnerDirection { String toJson() => value; - static const by = PartnerDirection._(r'shared-by'); - static const with_ = PartnerDirection._(r'shared-with'); + static const sharedBy = PartnerDirection._(r'shared-by'); + static const sharedWith = PartnerDirection._(r'shared-with'); /// List of all possible values in this [enum][PartnerDirection]. static const values = [ - by, - with_, + sharedBy, + sharedWith, ]; static PartnerDirection? fromJson(dynamic value) => PartnerDirectionTypeTransformer().decode(value); @@ -68,8 +68,8 @@ class PartnerDirectionTypeTransformer { PartnerDirection? decode(dynamic data, {bool allowNull = true}) { if (data != null) { switch (data) { - case r'shared-by': return PartnerDirection.by; - case r'shared-with': return PartnerDirection.with_; + case r'shared-by': return PartnerDirection.sharedBy; + case r'shared-with': return PartnerDirection.sharedWith; default: if (!allowNull) { throw ArgumentError('Unknown enum value to decode: $data'); diff --git a/mobile/openapi/lib/model/partner_response_dto.dart b/mobile/openapi/lib/model/partner_response_dto.dart index f4612cc98a..967c5b930b 100644 --- a/mobile/openapi/lib/model/partner_response_dto.dart +++ b/mobile/openapi/lib/model/partner_response_dto.dart @@ -16,7 +16,7 @@ class PartnerResponseDto { required this.avatarColor, required this.email, required this.id, - this.inTimeline, + this.inTimeline = const Optional.absent(), required this.name, required this.profileChangedAt, required this.profileImagePath, @@ -37,7 +37,7 @@ class PartnerResponseDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? inTimeline; + Optional inTimeline; /// User name String name; @@ -77,10 +77,9 @@ class PartnerResponseDto { json[r'avatarColor'] = this.avatarColor; json[r'email'] = this.email; json[r'id'] = this.id; - if (this.inTimeline != null) { - json[r'inTimeline'] = this.inTimeline; - } else { - // json[r'inTimeline'] = null; + if (this.inTimeline.isPresent) { + final value = this.inTimeline.value; + json[r'inTimeline'] = value; } json[r'name'] = this.name; json[r'profileChangedAt'] = this.profileChangedAt.toUtc().toIso8601String(); @@ -100,7 +99,7 @@ class PartnerResponseDto { avatarColor: UserAvatarColor.fromJson(json[r'avatarColor'])!, email: mapValueOfType(json, r'email')!, id: mapValueOfType(json, r'id')!, - inTimeline: mapValueOfType(json, r'inTimeline'), + inTimeline: json.containsKey(r'inTimeline') ? Optional.present(mapValueOfType(json, r'inTimeline')) : const Optional.absent(), name: mapValueOfType(json, r'name')!, profileChangedAt: mapDateTime(json, r'profileChangedAt', r'')!, profileImagePath: mapValueOfType(json, r'profileImagePath')!, diff --git a/mobile/openapi/lib/model/people_response.dart b/mobile/openapi/lib/model/people_response.dart index 9d5d8ec18a..838d1e2324 100644 --- a/mobile/openapi/lib/model/people_response.dart +++ b/mobile/openapi/lib/model/people_response.dart @@ -14,32 +14,51 @@ class PeopleResponse { /// Returns a new [PeopleResponse] instance. PeopleResponse({ required this.enabled, + this.minimumFaces = const Optional.absent(), required this.sidebarWeb, }); /// Whether people are enabled bool enabled; + /// People face threshold + /// + /// Minimum value: 1 + /// Maximum value: 9007199254740991 + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + Optional minimumFaces; + /// Whether people appear in web sidebar bool sidebarWeb; @override bool operator ==(Object other) => identical(this, other) || other is PeopleResponse && other.enabled == enabled && + other.minimumFaces == minimumFaces && other.sidebarWeb == sidebarWeb; @override int get hashCode => // ignore: unnecessary_parenthesis (enabled.hashCode) + + (minimumFaces == null ? 0 : minimumFaces!.hashCode) + (sidebarWeb.hashCode); @override - String toString() => 'PeopleResponse[enabled=$enabled, sidebarWeb=$sidebarWeb]'; + String toString() => 'PeopleResponse[enabled=$enabled, minimumFaces=$minimumFaces, sidebarWeb=$sidebarWeb]'; Map toJson() { final json = {}; json[r'enabled'] = this.enabled; + if (this.minimumFaces.isPresent) { + final value = this.minimumFaces.value; + json[r'minimumFaces'] = value; + } json[r'sidebarWeb'] = this.sidebarWeb; return json; } @@ -54,6 +73,7 @@ class PeopleResponse { return PeopleResponse( enabled: mapValueOfType(json, r'enabled')!, + minimumFaces: json.containsKey(r'minimumFaces') ? Optional.present(json[r'minimumFaces'] == null ? null : int.parse('${json[r'minimumFaces']}')) : const Optional.absent(), sidebarWeb: mapValueOfType(json, r'sidebarWeb')!, ); } diff --git a/mobile/openapi/lib/model/people_response_dto.dart b/mobile/openapi/lib/model/people_response_dto.dart index 87edc6b4a7..f9fc157239 100644 --- a/mobile/openapi/lib/model/people_response_dto.dart +++ b/mobile/openapi/lib/model/people_response_dto.dart @@ -13,7 +13,7 @@ part of openapi.api; class PeopleResponseDto { /// Returns a new [PeopleResponseDto] instance. PeopleResponseDto({ - this.hasNextPage, + this.hasNextPage = const Optional.absent(), required this.hidden, this.people = const [], required this.total, @@ -26,7 +26,7 @@ class PeopleResponseDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? hasNextPage; + Optional hasNextPage; /// Number of hidden people /// @@ -62,10 +62,9 @@ class PeopleResponseDto { Map toJson() { final json = {}; - if (this.hasNextPage != null) { - json[r'hasNextPage'] = this.hasNextPage; - } else { - // json[r'hasNextPage'] = null; + if (this.hasNextPage.isPresent) { + final value = this.hasNextPage.value; + json[r'hasNextPage'] = value; } json[r'hidden'] = this.hidden; json[r'people'] = this.people; @@ -82,7 +81,7 @@ class PeopleResponseDto { final json = value.cast(); return PeopleResponseDto( - hasNextPage: mapValueOfType(json, r'hasNextPage'), + hasNextPage: json.containsKey(r'hasNextPage') ? Optional.present(mapValueOfType(json, r'hasNextPage')) : const Optional.absent(), hidden: mapValueOfType(json, r'hidden')!, people: PersonResponseDto.listFromJson(json[r'people']), total: mapValueOfType(json, r'total')!, diff --git a/mobile/openapi/lib/model/people_update.dart b/mobile/openapi/lib/model/people_update.dart index fe16479bac..ea8ae73138 100644 --- a/mobile/openapi/lib/model/people_update.dart +++ b/mobile/openapi/lib/model/people_update.dart @@ -13,8 +13,9 @@ part of openapi.api; class PeopleUpdate { /// Returns a new [PeopleUpdate] instance. PeopleUpdate({ - this.enabled, - this.sidebarWeb, + this.enabled = const Optional.absent(), + this.minimumFaces = const Optional.absent(), + this.sidebarWeb = const Optional.absent(), }); /// Whether people are enabled @@ -24,7 +25,19 @@ class PeopleUpdate { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? enabled; + Optional enabled; + + /// People face threshold + /// + /// Minimum value: 1 + /// Maximum value: 9007199254740991 + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + Optional minimumFaces; /// Whether people appear in web sidebar /// @@ -33,33 +46,37 @@ class PeopleUpdate { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? sidebarWeb; + Optional sidebarWeb; @override bool operator ==(Object other) => identical(this, other) || other is PeopleUpdate && other.enabled == enabled && + other.minimumFaces == minimumFaces && other.sidebarWeb == sidebarWeb; @override int get hashCode => // ignore: unnecessary_parenthesis (enabled == null ? 0 : enabled!.hashCode) + + (minimumFaces == null ? 0 : minimumFaces!.hashCode) + (sidebarWeb == null ? 0 : sidebarWeb!.hashCode); @override - String toString() => 'PeopleUpdate[enabled=$enabled, sidebarWeb=$sidebarWeb]'; + String toString() => 'PeopleUpdate[enabled=$enabled, minimumFaces=$minimumFaces, sidebarWeb=$sidebarWeb]'; Map toJson() { final json = {}; - if (this.enabled != null) { - json[r'enabled'] = this.enabled; - } else { - // json[r'enabled'] = null; + if (this.enabled.isPresent) { + final value = this.enabled.value; + json[r'enabled'] = value; } - if (this.sidebarWeb != null) { - json[r'sidebarWeb'] = this.sidebarWeb; - } else { - // json[r'sidebarWeb'] = null; + if (this.minimumFaces.isPresent) { + final value = this.minimumFaces.value; + json[r'minimumFaces'] = value; + } + if (this.sidebarWeb.isPresent) { + final value = this.sidebarWeb.value; + json[r'sidebarWeb'] = value; } return json; } @@ -73,8 +90,9 @@ class PeopleUpdate { final json = value.cast(); return PeopleUpdate( - enabled: mapValueOfType(json, r'enabled'), - sidebarWeb: mapValueOfType(json, r'sidebarWeb'), + enabled: json.containsKey(r'enabled') ? Optional.present(mapValueOfType(json, r'enabled')) : const Optional.absent(), + minimumFaces: json.containsKey(r'minimumFaces') ? Optional.present(json[r'minimumFaces'] == null ? null : int.parse('${json[r'minimumFaces']}')) : const Optional.absent(), + sidebarWeb: json.containsKey(r'sidebarWeb') ? Optional.present(mapValueOfType(json, r'sidebarWeb')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/people_update_item.dart b/mobile/openapi/lib/model/people_update_item.dart index 5e20aeb464..9ebbd93e28 100644 --- a/mobile/openapi/lib/model/people_update_item.dart +++ b/mobile/openapi/lib/model/people_update_item.dart @@ -13,20 +13,20 @@ part of openapi.api; class PeopleUpdateItem { /// Returns a new [PeopleUpdateItem] instance. PeopleUpdateItem({ - this.birthDate, - this.color, - this.featureFaceAssetId, + this.birthDate = const Optional.absent(), + this.color = const Optional.absent(), + this.featureFaceAssetId = const Optional.absent(), required this.id, - this.isFavorite, - this.isHidden, - this.name, + this.isFavorite = const Optional.absent(), + this.isHidden = const Optional.absent(), + this.name = const Optional.absent(), }); /// Person date of birth - DateTime? birthDate; + Optional birthDate; /// Person color (hex) - String? color; + Optional color; /// Asset ID used for feature face thumbnail /// @@ -35,7 +35,7 @@ class PeopleUpdateItem { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? featureFaceAssetId; + Optional featureFaceAssetId; /// Person ID String id; @@ -47,7 +47,7 @@ class PeopleUpdateItem { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? isFavorite; + Optional isFavorite; /// Person visibility (hidden) /// @@ -56,7 +56,7 @@ class PeopleUpdateItem { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? isHidden; + Optional isHidden; /// Person name /// @@ -65,7 +65,7 @@ class PeopleUpdateItem { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? name; + Optional name; @override bool operator ==(Object other) => identical(this, other) || other is PeopleUpdateItem && @@ -93,36 +93,30 @@ class PeopleUpdateItem { Map toJson() { final json = {}; - if (this.birthDate != null) { - json[r'birthDate'] = _dateFormatter.format(this.birthDate!.toUtc()); - } else { - // json[r'birthDate'] = null; + if (this.birthDate.isPresent) { + final value = this.birthDate.value; + json[r'birthDate'] = value == null ? null : _dateFormatter.format(value.toUtc()); } - if (this.color != null) { - json[r'color'] = this.color; - } else { - // json[r'color'] = null; + if (this.color.isPresent) { + final value = this.color.value; + json[r'color'] = value; } - if (this.featureFaceAssetId != null) { - json[r'featureFaceAssetId'] = this.featureFaceAssetId; - } else { - // json[r'featureFaceAssetId'] = null; + if (this.featureFaceAssetId.isPresent) { + final value = this.featureFaceAssetId.value; + json[r'featureFaceAssetId'] = value; } json[r'id'] = this.id; - if (this.isFavorite != null) { - json[r'isFavorite'] = this.isFavorite; - } else { - // json[r'isFavorite'] = null; + if (this.isFavorite.isPresent) { + final value = this.isFavorite.value; + json[r'isFavorite'] = value; } - if (this.isHidden != null) { - json[r'isHidden'] = this.isHidden; - } else { - // json[r'isHidden'] = null; + if (this.isHidden.isPresent) { + final value = this.isHidden.value; + json[r'isHidden'] = value; } - if (this.name != null) { - json[r'name'] = this.name; - } else { - // json[r'name'] = null; + if (this.name.isPresent) { + final value = this.name.value; + json[r'name'] = value; } return json; } @@ -136,13 +130,13 @@ class PeopleUpdateItem { final json = value.cast(); return PeopleUpdateItem( - birthDate: mapDateTime(json, r'birthDate', r''), - color: mapValueOfType(json, r'color'), - featureFaceAssetId: mapValueOfType(json, r'featureFaceAssetId'), + birthDate: json.containsKey(r'birthDate') ? Optional.present(mapDateTime(json, r'birthDate', r'')) : const Optional.absent(), + color: json.containsKey(r'color') ? Optional.present(mapValueOfType(json, r'color')) : const Optional.absent(), + featureFaceAssetId: json.containsKey(r'featureFaceAssetId') ? Optional.present(mapValueOfType(json, r'featureFaceAssetId')) : const Optional.absent(), id: mapValueOfType(json, r'id')!, - isFavorite: mapValueOfType(json, r'isFavorite'), - isHidden: mapValueOfType(json, r'isHidden'), - name: mapValueOfType(json, r'name'), + isFavorite: json.containsKey(r'isFavorite') ? Optional.present(mapValueOfType(json, r'isFavorite')) : const Optional.absent(), + isHidden: json.containsKey(r'isHidden') ? Optional.present(mapValueOfType(json, r'isHidden')) : const Optional.absent(), + name: json.containsKey(r'name') ? Optional.present(mapValueOfType(json, r'name')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/person_create_dto.dart b/mobile/openapi/lib/model/person_create_dto.dart index f2ba702c2f..22c9d2fb4c 100644 --- a/mobile/openapi/lib/model/person_create_dto.dart +++ b/mobile/openapi/lib/model/person_create_dto.dart @@ -13,18 +13,18 @@ part of openapi.api; class PersonCreateDto { /// Returns a new [PersonCreateDto] instance. PersonCreateDto({ - this.birthDate, - this.color, - this.isFavorite, - this.isHidden, - this.name, + this.birthDate = const Optional.absent(), + this.color = const Optional.absent(), + this.isFavorite = const Optional.absent(), + this.isHidden = const Optional.absent(), + this.name = const Optional.absent(), }); /// Person date of birth - DateTime? birthDate; + Optional birthDate; /// Person color (hex) - String? color; + Optional color; /// Mark as favorite /// @@ -33,7 +33,7 @@ class PersonCreateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? isFavorite; + Optional isFavorite; /// Person visibility (hidden) /// @@ -42,7 +42,7 @@ class PersonCreateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? isHidden; + Optional isHidden; /// Person name /// @@ -51,7 +51,7 @@ class PersonCreateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? name; + Optional name; @override bool operator ==(Object other) => identical(this, other) || other is PersonCreateDto && @@ -75,30 +75,25 @@ class PersonCreateDto { Map toJson() { final json = {}; - if (this.birthDate != null) { - json[r'birthDate'] = _dateFormatter.format(this.birthDate!.toUtc()); - } else { - // json[r'birthDate'] = null; + if (this.birthDate.isPresent) { + final value = this.birthDate.value; + json[r'birthDate'] = value == null ? null : _dateFormatter.format(value.toUtc()); } - if (this.color != null) { - json[r'color'] = this.color; - } else { - // json[r'color'] = null; + if (this.color.isPresent) { + final value = this.color.value; + json[r'color'] = value; } - if (this.isFavorite != null) { - json[r'isFavorite'] = this.isFavorite; - } else { - // json[r'isFavorite'] = null; + if (this.isFavorite.isPresent) { + final value = this.isFavorite.value; + json[r'isFavorite'] = value; } - if (this.isHidden != null) { - json[r'isHidden'] = this.isHidden; - } else { - // json[r'isHidden'] = null; + if (this.isHidden.isPresent) { + final value = this.isHidden.value; + json[r'isHidden'] = value; } - if (this.name != null) { - json[r'name'] = this.name; - } else { - // json[r'name'] = null; + if (this.name.isPresent) { + final value = this.name.value; + json[r'name'] = value; } return json; } @@ -112,11 +107,11 @@ class PersonCreateDto { final json = value.cast(); return PersonCreateDto( - birthDate: mapDateTime(json, r'birthDate', r''), - color: mapValueOfType(json, r'color'), - isFavorite: mapValueOfType(json, r'isFavorite'), - isHidden: mapValueOfType(json, r'isHidden'), - name: mapValueOfType(json, r'name'), + birthDate: json.containsKey(r'birthDate') ? Optional.present(mapDateTime(json, r'birthDate', r'')) : const Optional.absent(), + color: json.containsKey(r'color') ? Optional.present(mapValueOfType(json, r'color')) : const Optional.absent(), + isFavorite: json.containsKey(r'isFavorite') ? Optional.present(mapValueOfType(json, r'isFavorite')) : const Optional.absent(), + isHidden: json.containsKey(r'isHidden') ? Optional.present(mapValueOfType(json, r'isHidden')) : const Optional.absent(), + name: json.containsKey(r'name') ? Optional.present(mapValueOfType(json, r'name')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/person_response_dto.dart b/mobile/openapi/lib/model/person_response_dto.dart index 455dfb98d6..a99f465236 100644 --- a/mobile/openapi/lib/model/person_response_dto.dart +++ b/mobile/openapi/lib/model/person_response_dto.dart @@ -14,13 +14,13 @@ class PersonResponseDto { /// Returns a new [PersonResponseDto] instance. PersonResponseDto({ required this.birthDate, - this.color, + this.color = const Optional.absent(), required this.id, - this.isFavorite, + this.isFavorite = const Optional.absent(), required this.isHidden, required this.name, required this.thumbnailPath, - this.updatedAt, + this.updatedAt = const Optional.absent(), }); /// Person date of birth @@ -33,7 +33,7 @@ class PersonResponseDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? color; + Optional color; /// Person ID String id; @@ -45,7 +45,7 @@ class PersonResponseDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? isFavorite; + Optional isFavorite; /// Is hidden bool isHidden; @@ -63,7 +63,7 @@ class PersonResponseDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - DateTime? updatedAt; + Optional updatedAt; @override bool operator ==(Object other) => identical(this, other) || other is PersonResponseDto && @@ -96,26 +96,23 @@ class PersonResponseDto { if (this.birthDate != null) { json[r'birthDate'] = _dateFormatter.format(this.birthDate!.toUtc()); } else { - // json[r'birthDate'] = null; + json[r'birthDate'] = null; } - if (this.color != null) { - json[r'color'] = this.color; - } else { - // json[r'color'] = null; + if (this.color.isPresent) { + final value = this.color.value; + json[r'color'] = value; } json[r'id'] = this.id; - if (this.isFavorite != null) { - json[r'isFavorite'] = this.isFavorite; - } else { - // json[r'isFavorite'] = null; + if (this.isFavorite.isPresent) { + final value = this.isFavorite.value; + json[r'isFavorite'] = value; } json[r'isHidden'] = this.isHidden; json[r'name'] = this.name; json[r'thumbnailPath'] = this.thumbnailPath; - if (this.updatedAt != null) { - json[r'updatedAt'] = this.updatedAt!.toUtc().toIso8601String(); - } else { - // json[r'updatedAt'] = null; + if (this.updatedAt.isPresent) { + final value = this.updatedAt.value; + json[r'updatedAt'] = value == null ? null : value.toUtc().toIso8601String(); } return json; } @@ -130,13 +127,13 @@ class PersonResponseDto { return PersonResponseDto( birthDate: mapDateTime(json, r'birthDate', r''), - color: mapValueOfType(json, r'color'), + color: json.containsKey(r'color') ? Optional.present(mapValueOfType(json, r'color')) : const Optional.absent(), id: mapValueOfType(json, r'id')!, - isFavorite: mapValueOfType(json, r'isFavorite'), + isFavorite: json.containsKey(r'isFavorite') ? Optional.present(mapValueOfType(json, r'isFavorite')) : const Optional.absent(), isHidden: mapValueOfType(json, r'isHidden')!, name: mapValueOfType(json, r'name')!, thumbnailPath: mapValueOfType(json, r'thumbnailPath')!, - updatedAt: mapDateTime(json, r'updatedAt', r''), + updatedAt: json.containsKey(r'updatedAt') ? Optional.present(mapDateTime(json, r'updatedAt', r'')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/person_update_dto.dart b/mobile/openapi/lib/model/person_update_dto.dart index b56940e51d..56b99606ee 100644 --- a/mobile/openapi/lib/model/person_update_dto.dart +++ b/mobile/openapi/lib/model/person_update_dto.dart @@ -13,19 +13,19 @@ part of openapi.api; class PersonUpdateDto { /// Returns a new [PersonUpdateDto] instance. PersonUpdateDto({ - this.birthDate, - this.color, - this.featureFaceAssetId, - this.isFavorite, - this.isHidden, - this.name, + this.birthDate = const Optional.absent(), + this.color = const Optional.absent(), + this.featureFaceAssetId = const Optional.absent(), + this.isFavorite = const Optional.absent(), + this.isHidden = const Optional.absent(), + this.name = const Optional.absent(), }); /// Person date of birth - DateTime? birthDate; + Optional birthDate; /// Person color (hex) - String? color; + Optional color; /// Asset ID used for feature face thumbnail /// @@ -34,7 +34,7 @@ class PersonUpdateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? featureFaceAssetId; + Optional featureFaceAssetId; /// Mark as favorite /// @@ -43,7 +43,7 @@ class PersonUpdateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? isFavorite; + Optional isFavorite; /// Person visibility (hidden) /// @@ -52,7 +52,7 @@ class PersonUpdateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? isHidden; + Optional isHidden; /// Person name /// @@ -61,7 +61,7 @@ class PersonUpdateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? name; + Optional name; @override bool operator ==(Object other) => identical(this, other) || other is PersonUpdateDto && @@ -87,35 +87,29 @@ class PersonUpdateDto { Map toJson() { final json = {}; - if (this.birthDate != null) { - json[r'birthDate'] = _dateFormatter.format(this.birthDate!.toUtc()); - } else { - // json[r'birthDate'] = null; + if (this.birthDate.isPresent) { + final value = this.birthDate.value; + json[r'birthDate'] = value == null ? null : _dateFormatter.format(value.toUtc()); } - if (this.color != null) { - json[r'color'] = this.color; - } else { - // json[r'color'] = null; + if (this.color.isPresent) { + final value = this.color.value; + json[r'color'] = value; } - if (this.featureFaceAssetId != null) { - json[r'featureFaceAssetId'] = this.featureFaceAssetId; - } else { - // json[r'featureFaceAssetId'] = null; + if (this.featureFaceAssetId.isPresent) { + final value = this.featureFaceAssetId.value; + json[r'featureFaceAssetId'] = value; } - if (this.isFavorite != null) { - json[r'isFavorite'] = this.isFavorite; - } else { - // json[r'isFavorite'] = null; + if (this.isFavorite.isPresent) { + final value = this.isFavorite.value; + json[r'isFavorite'] = value; } - if (this.isHidden != null) { - json[r'isHidden'] = this.isHidden; - } else { - // json[r'isHidden'] = null; + if (this.isHidden.isPresent) { + final value = this.isHidden.value; + json[r'isHidden'] = value; } - if (this.name != null) { - json[r'name'] = this.name; - } else { - // json[r'name'] = null; + if (this.name.isPresent) { + final value = this.name.value; + json[r'name'] = value; } return json; } @@ -129,12 +123,12 @@ class PersonUpdateDto { final json = value.cast(); return PersonUpdateDto( - birthDate: mapDateTime(json, r'birthDate', r''), - color: mapValueOfType(json, r'color'), - featureFaceAssetId: mapValueOfType(json, r'featureFaceAssetId'), - isFavorite: mapValueOfType(json, r'isFavorite'), - isHidden: mapValueOfType(json, r'isHidden'), - name: mapValueOfType(json, r'name'), + birthDate: json.containsKey(r'birthDate') ? Optional.present(mapDateTime(json, r'birthDate', r'')) : const Optional.absent(), + color: json.containsKey(r'color') ? Optional.present(mapValueOfType(json, r'color')) : const Optional.absent(), + featureFaceAssetId: json.containsKey(r'featureFaceAssetId') ? Optional.present(mapValueOfType(json, r'featureFaceAssetId')) : const Optional.absent(), + isFavorite: json.containsKey(r'isFavorite') ? Optional.present(mapValueOfType(json, r'isFavorite')) : const Optional.absent(), + isHidden: json.containsKey(r'isHidden') ? Optional.present(mapValueOfType(json, r'isHidden')) : const Optional.absent(), + name: json.containsKey(r'name') ? Optional.present(mapValueOfType(json, r'name')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/person_with_faces_response_dto.dart b/mobile/openapi/lib/model/person_with_faces_response_dto.dart deleted file mode 100644 index f710dff8b9..0000000000 --- a/mobile/openapi/lib/model/person_with_faces_response_dto.dart +++ /dev/null @@ -1,202 +0,0 @@ -// -// AUTO-GENERATED FILE, DO NOT MODIFY! -// -// @dart=2.18 - -// ignore_for_file: unused_element, unused_import -// ignore_for_file: always_put_required_named_parameters_first -// ignore_for_file: constant_identifier_names -// ignore_for_file: lines_longer_than_80_chars - -part of openapi.api; - -class PersonWithFacesResponseDto { - /// Returns a new [PersonWithFacesResponseDto] instance. - PersonWithFacesResponseDto({ - required this.birthDate, - this.color, - this.faces = const [], - required this.id, - this.isFavorite, - required this.isHidden, - required this.name, - required this.thumbnailPath, - this.updatedAt, - }); - - /// Person date of birth - DateTime? birthDate; - - /// Person color (hex) - /// - /// Please note: This property should have been non-nullable! Since the specification file - /// does not include a default value (using the "default:" property), however, the generated - /// source code must fall back to having a nullable type. - /// Consider adding a "default:" property in the specification file to hide this note. - /// - String? color; - - List faces; - - /// Person ID - String id; - - /// Is favorite - /// - /// Please note: This property should have been non-nullable! Since the specification file - /// does not include a default value (using the "default:" property), however, the generated - /// source code must fall back to having a nullable type. - /// Consider adding a "default:" property in the specification file to hide this note. - /// - bool? isFavorite; - - /// Is hidden - bool isHidden; - - /// Person name - String name; - - /// Thumbnail path - String thumbnailPath; - - /// Last update date - /// - /// Please note: This property should have been non-nullable! Since the specification file - /// does not include a default value (using the "default:" property), however, the generated - /// source code must fall back to having a nullable type. - /// Consider adding a "default:" property in the specification file to hide this note. - /// - DateTime? updatedAt; - - @override - bool operator ==(Object other) => identical(this, other) || other is PersonWithFacesResponseDto && - other.birthDate == birthDate && - other.color == color && - _deepEquality.equals(other.faces, faces) && - other.id == id && - other.isFavorite == isFavorite && - other.isHidden == isHidden && - other.name == name && - other.thumbnailPath == thumbnailPath && - other.updatedAt == updatedAt; - - @override - int get hashCode => - // ignore: unnecessary_parenthesis - (birthDate == null ? 0 : birthDate!.hashCode) + - (color == null ? 0 : color!.hashCode) + - (faces.hashCode) + - (id.hashCode) + - (isFavorite == null ? 0 : isFavorite!.hashCode) + - (isHidden.hashCode) + - (name.hashCode) + - (thumbnailPath.hashCode) + - (updatedAt == null ? 0 : updatedAt!.hashCode); - - @override - String toString() => 'PersonWithFacesResponseDto[birthDate=$birthDate, color=$color, faces=$faces, id=$id, isFavorite=$isFavorite, isHidden=$isHidden, name=$name, thumbnailPath=$thumbnailPath, updatedAt=$updatedAt]'; - - Map toJson() { - final json = {}; - if (this.birthDate != null) { - json[r'birthDate'] = _dateFormatter.format(this.birthDate!.toUtc()); - } else { - // json[r'birthDate'] = null; - } - if (this.color != null) { - json[r'color'] = this.color; - } else { - // json[r'color'] = null; - } - json[r'faces'] = this.faces; - json[r'id'] = this.id; - if (this.isFavorite != null) { - json[r'isFavorite'] = this.isFavorite; - } else { - // json[r'isFavorite'] = null; - } - json[r'isHidden'] = this.isHidden; - json[r'name'] = this.name; - json[r'thumbnailPath'] = this.thumbnailPath; - if (this.updatedAt != null) { - json[r'updatedAt'] = this.updatedAt!.toUtc().toIso8601String(); - } else { - // json[r'updatedAt'] = null; - } - return json; - } - - /// Returns a new [PersonWithFacesResponseDto] instance and imports its values from - /// [value] if it's a [Map], null otherwise. - // ignore: prefer_constructors_over_static_methods - static PersonWithFacesResponseDto? fromJson(dynamic value) { - upgradeDto(value, "PersonWithFacesResponseDto"); - if (value is Map) { - final json = value.cast(); - - return PersonWithFacesResponseDto( - birthDate: mapDateTime(json, r'birthDate', r''), - color: mapValueOfType(json, r'color'), - faces: AssetFaceWithoutPersonResponseDto.listFromJson(json[r'faces']), - id: mapValueOfType(json, r'id')!, - isFavorite: mapValueOfType(json, r'isFavorite'), - isHidden: mapValueOfType(json, r'isHidden')!, - name: mapValueOfType(json, r'name')!, - thumbnailPath: mapValueOfType(json, r'thumbnailPath')!, - updatedAt: mapDateTime(json, r'updatedAt', r''), - ); - } - return null; - } - - static List listFromJson(dynamic json, {bool growable = false,}) { - final result = []; - if (json is List && json.isNotEmpty) { - for (final row in json) { - final value = PersonWithFacesResponseDto.fromJson(row); - if (value != null) { - result.add(value); - } - } - } - return result.toList(growable: growable); - } - - static Map mapFromJson(dynamic json) { - final map = {}; - if (json is Map && json.isNotEmpty) { - json = json.cast(); // ignore: parameter_assignments - for (final entry in json.entries) { - final value = PersonWithFacesResponseDto.fromJson(entry.value); - if (value != null) { - map[entry.key] = value; - } - } - } - return map; - } - - // maps a json object with a list of PersonWithFacesResponseDto-objects as value to a dart map - static Map> mapListFromJson(dynamic json, {bool growable = false,}) { - final map = >{}; - if (json is Map && json.isNotEmpty) { - // ignore: parameter_assignments - json = json.cast(); - for (final entry in json.entries) { - map[entry.key] = PersonWithFacesResponseDto.listFromJson(entry.value, growable: growable,); - } - } - return map; - } - - /// The list of required keys that must be present in a JSON. - static const requiredKeys = { - 'birthDate', - 'faces', - 'id', - 'isHidden', - 'name', - 'thumbnailPath', - }; -} - diff --git a/mobile/openapi/lib/model/pin_code_change_dto.dart b/mobile/openapi/lib/model/pin_code_change_dto.dart index 068cc9e91b..42c244933f 100644 --- a/mobile/openapi/lib/model/pin_code_change_dto.dart +++ b/mobile/openapi/lib/model/pin_code_change_dto.dart @@ -14,8 +14,8 @@ class PinCodeChangeDto { /// Returns a new [PinCodeChangeDto] instance. PinCodeChangeDto({ required this.newPinCode, - this.password, - this.pinCode, + this.password = const Optional.absent(), + this.pinCode = const Optional.absent(), }); /// New PIN code (4-6 digits) @@ -28,7 +28,7 @@ class PinCodeChangeDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? password; + Optional password; /// New PIN code (4-6 digits) /// @@ -37,7 +37,7 @@ class PinCodeChangeDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? pinCode; + Optional pinCode; @override bool operator ==(Object other) => identical(this, other) || other is PinCodeChangeDto && @@ -58,15 +58,13 @@ class PinCodeChangeDto { Map toJson() { final json = {}; json[r'newPinCode'] = this.newPinCode; - if (this.password != null) { - json[r'password'] = this.password; - } else { - // json[r'password'] = null; + if (this.password.isPresent) { + final value = this.password.value; + json[r'password'] = value; } - if (this.pinCode != null) { - json[r'pinCode'] = this.pinCode; - } else { - // json[r'pinCode'] = null; + if (this.pinCode.isPresent) { + final value = this.pinCode.value; + json[r'pinCode'] = value; } return json; } @@ -81,8 +79,8 @@ class PinCodeChangeDto { return PinCodeChangeDto( newPinCode: mapValueOfType(json, r'newPinCode')!, - password: mapValueOfType(json, r'password'), - pinCode: mapValueOfType(json, r'pinCode'), + password: json.containsKey(r'password') ? Optional.present(mapValueOfType(json, r'password')) : const Optional.absent(), + pinCode: json.containsKey(r'pinCode') ? Optional.present(mapValueOfType(json, r'pinCode')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/pin_code_reset_dto.dart b/mobile/openapi/lib/model/pin_code_reset_dto.dart index c37be76f18..04ad61eeeb 100644 --- a/mobile/openapi/lib/model/pin_code_reset_dto.dart +++ b/mobile/openapi/lib/model/pin_code_reset_dto.dart @@ -13,8 +13,8 @@ part of openapi.api; class PinCodeResetDto { /// Returns a new [PinCodeResetDto] instance. PinCodeResetDto({ - this.password, - this.pinCode, + this.password = const Optional.absent(), + this.pinCode = const Optional.absent(), }); /// User password (required if PIN code is not provided) @@ -24,7 +24,7 @@ class PinCodeResetDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? password; + Optional password; /// New PIN code (4-6 digits) /// @@ -33,7 +33,7 @@ class PinCodeResetDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? pinCode; + Optional pinCode; @override bool operator ==(Object other) => identical(this, other) || other is PinCodeResetDto && @@ -51,15 +51,13 @@ class PinCodeResetDto { Map toJson() { final json = {}; - if (this.password != null) { - json[r'password'] = this.password; - } else { - // json[r'password'] = null; + if (this.password.isPresent) { + final value = this.password.value; + json[r'password'] = value; } - if (this.pinCode != null) { - json[r'pinCode'] = this.pinCode; - } else { - // json[r'pinCode'] = null; + if (this.pinCode.isPresent) { + final value = this.pinCode.value; + json[r'pinCode'] = value; } return json; } @@ -73,8 +71,8 @@ class PinCodeResetDto { final json = value.cast(); return PinCodeResetDto( - password: mapValueOfType(json, r'password'), - pinCode: mapValueOfType(json, r'pinCode'), + password: json.containsKey(r'password') ? Optional.present(mapValueOfType(json, r'password')) : const Optional.absent(), + pinCode: json.containsKey(r'pinCode') ? Optional.present(mapValueOfType(json, r'pinCode')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/places_response_dto.dart b/mobile/openapi/lib/model/places_response_dto.dart index 94aa58eba4..f222c33dba 100644 --- a/mobile/openapi/lib/model/places_response_dto.dart +++ b/mobile/openapi/lib/model/places_response_dto.dart @@ -13,8 +13,8 @@ part of openapi.api; class PlacesResponseDto { /// Returns a new [PlacesResponseDto] instance. PlacesResponseDto({ - this.admin1name, - this.admin2name, + this.admin1name = const Optional.absent(), + this.admin2name = const Optional.absent(), required this.latitude, required this.longitude, required this.name, @@ -27,7 +27,7 @@ class PlacesResponseDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? admin1name; + Optional admin1name; /// Administrative level 2 name (county/district) /// @@ -36,7 +36,7 @@ class PlacesResponseDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? admin2name; + Optional admin2name; /// Latitude coordinate num latitude; @@ -69,15 +69,13 @@ class PlacesResponseDto { Map toJson() { final json = {}; - if (this.admin1name != null) { - json[r'admin1name'] = this.admin1name; - } else { - // json[r'admin1name'] = null; + if (this.admin1name.isPresent) { + final value = this.admin1name.value; + json[r'admin1name'] = value; } - if (this.admin2name != null) { - json[r'admin2name'] = this.admin2name; - } else { - // json[r'admin2name'] = null; + if (this.admin2name.isPresent) { + final value = this.admin2name.value; + json[r'admin2name'] = value; } json[r'latitude'] = this.latitude; json[r'longitude'] = this.longitude; @@ -94,8 +92,8 @@ class PlacesResponseDto { final json = value.cast(); return PlacesResponseDto( - admin1name: mapValueOfType(json, r'admin1name'), - admin2name: mapValueOfType(json, r'admin2name'), + admin1name: json.containsKey(r'admin1name') ? Optional.present(mapValueOfType(json, r'admin1name')) : const Optional.absent(), + admin2name: json.containsKey(r'admin2name') ? Optional.present(mapValueOfType(json, r'admin2name')) : const Optional.absent(), latitude: num.parse('${json[r'latitude']}'), longitude: num.parse('${json[r'longitude']}'), name: mapValueOfType(json, r'name')!, diff --git a/mobile/openapi/lib/model/plugin_action_response_dto.dart b/mobile/openapi/lib/model/plugin_action_response_dto.dart deleted file mode 100644 index cff2dc92f7..0000000000 --- a/mobile/openapi/lib/model/plugin_action_response_dto.dart +++ /dev/null @@ -1,158 +0,0 @@ -// -// AUTO-GENERATED FILE, DO NOT MODIFY! -// -// @dart=2.18 - -// ignore_for_file: unused_element, unused_import -// ignore_for_file: always_put_required_named_parameters_first -// ignore_for_file: constant_identifier_names -// ignore_for_file: lines_longer_than_80_chars - -part of openapi.api; - -class PluginActionResponseDto { - /// Returns a new [PluginActionResponseDto] instance. - PluginActionResponseDto({ - required this.description, - required this.id, - required this.methodName, - required this.pluginId, - required this.schema, - this.supportedContexts = const [], - required this.title, - }); - - /// Action description - String description; - - /// Action ID - String id; - - /// Method name - String methodName; - - /// Plugin ID - String pluginId; - - /// Action schema - PluginJsonSchema? schema; - - /// Supported contexts - List supportedContexts; - - /// Action title - String title; - - @override - bool operator ==(Object other) => identical(this, other) || other is PluginActionResponseDto && - other.description == description && - other.id == id && - other.methodName == methodName && - other.pluginId == pluginId && - other.schema == schema && - _deepEquality.equals(other.supportedContexts, supportedContexts) && - other.title == title; - - @override - int get hashCode => - // ignore: unnecessary_parenthesis - (description.hashCode) + - (id.hashCode) + - (methodName.hashCode) + - (pluginId.hashCode) + - (schema == null ? 0 : schema!.hashCode) + - (supportedContexts.hashCode) + - (title.hashCode); - - @override - String toString() => 'PluginActionResponseDto[description=$description, id=$id, methodName=$methodName, pluginId=$pluginId, schema=$schema, supportedContexts=$supportedContexts, title=$title]'; - - Map toJson() { - final json = {}; - json[r'description'] = this.description; - json[r'id'] = this.id; - json[r'methodName'] = this.methodName; - json[r'pluginId'] = this.pluginId; - if (this.schema != null) { - json[r'schema'] = this.schema; - } else { - // json[r'schema'] = null; - } - json[r'supportedContexts'] = this.supportedContexts; - json[r'title'] = this.title; - return json; - } - - /// Returns a new [PluginActionResponseDto] instance and imports its values from - /// [value] if it's a [Map], null otherwise. - // ignore: prefer_constructors_over_static_methods - static PluginActionResponseDto? fromJson(dynamic value) { - upgradeDto(value, "PluginActionResponseDto"); - if (value is Map) { - final json = value.cast(); - - return PluginActionResponseDto( - description: mapValueOfType(json, r'description')!, - id: mapValueOfType(json, r'id')!, - methodName: mapValueOfType(json, r'methodName')!, - pluginId: mapValueOfType(json, r'pluginId')!, - schema: PluginJsonSchema.fromJson(json[r'schema']), - supportedContexts: PluginContextType.listFromJson(json[r'supportedContexts']), - title: mapValueOfType(json, r'title')!, - ); - } - return null; - } - - static List listFromJson(dynamic json, {bool growable = false,}) { - final result = []; - if (json is List && json.isNotEmpty) { - for (final row in json) { - final value = PluginActionResponseDto.fromJson(row); - if (value != null) { - result.add(value); - } - } - } - return result.toList(growable: growable); - } - - static Map mapFromJson(dynamic json) { - final map = {}; - if (json is Map && json.isNotEmpty) { - json = json.cast(); // ignore: parameter_assignments - for (final entry in json.entries) { - final value = PluginActionResponseDto.fromJson(entry.value); - if (value != null) { - map[entry.key] = value; - } - } - } - return map; - } - - // maps a json object with a list of PluginActionResponseDto-objects as value to a dart map - static Map> mapListFromJson(dynamic json, {bool growable = false,}) { - final map = >{}; - if (json is Map && json.isNotEmpty) { - // ignore: parameter_assignments - json = json.cast(); - for (final entry in json.entries) { - map[entry.key] = PluginActionResponseDto.listFromJson(entry.value, growable: growable,); - } - } - return map; - } - - /// The list of required keys that must be present in a JSON. - static const requiredKeys = { - 'description', - 'id', - 'methodName', - 'pluginId', - 'schema', - 'supportedContexts', - 'title', - }; -} - diff --git a/mobile/openapi/lib/model/plugin_context_type.dart b/mobile/openapi/lib/model/plugin_context_type.dart deleted file mode 100644 index beda0b0f1a..0000000000 --- a/mobile/openapi/lib/model/plugin_context_type.dart +++ /dev/null @@ -1,88 +0,0 @@ -// -// AUTO-GENERATED FILE, DO NOT MODIFY! -// -// @dart=2.18 - -// ignore_for_file: unused_element, unused_import -// ignore_for_file: always_put_required_named_parameters_first -// ignore_for_file: constant_identifier_names -// ignore_for_file: lines_longer_than_80_chars - -part of openapi.api; - -/// Plugin context -class PluginContextType { - /// Instantiate a new enum with the provided [value]. - const PluginContextType._(this.value); - - /// The underlying value of this enum member. - final String value; - - @override - String toString() => value; - - String toJson() => value; - - static const asset = PluginContextType._(r'asset'); - static const album = PluginContextType._(r'album'); - static const person = PluginContextType._(r'person'); - - /// List of all possible values in this [enum][PluginContextType]. - static const values = [ - asset, - album, - person, - ]; - - static PluginContextType? fromJson(dynamic value) => PluginContextTypeTypeTransformer().decode(value); - - static List listFromJson(dynamic json, {bool growable = false,}) { - final result = []; - if (json is List && json.isNotEmpty) { - for (final row in json) { - final value = PluginContextType.fromJson(row); - if (value != null) { - result.add(value); - } - } - } - return result.toList(growable: growable); - } -} - -/// Transformation class that can [encode] an instance of [PluginContextType] to String, -/// and [decode] dynamic data back to [PluginContextType]. -class PluginContextTypeTypeTransformer { - factory PluginContextTypeTypeTransformer() => _instance ??= const PluginContextTypeTypeTransformer._(); - - const PluginContextTypeTypeTransformer._(); - - String encode(PluginContextType data) => data.value; - - /// Decodes a [dynamic value][data] to a PluginContextType. - /// - /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, - /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] - /// cannot be decoded successfully, then an [UnimplementedError] is thrown. - /// - /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, - /// and users are still using an old app with the old code. - PluginContextType? decode(dynamic data, {bool allowNull = true}) { - if (data != null) { - switch (data) { - case r'asset': return PluginContextType.asset; - case r'album': return PluginContextType.album; - case r'person': return PluginContextType.person; - default: - if (!allowNull) { - throw ArgumentError('Unknown enum value to decode: $data'); - } - } - } - return null; - } - - /// Singleton [PluginContextTypeTypeTransformer] instance. - static PluginContextTypeTypeTransformer? _instance; -} - diff --git a/mobile/openapi/lib/model/plugin_filter_response_dto.dart b/mobile/openapi/lib/model/plugin_filter_response_dto.dart deleted file mode 100644 index d1ab867ff9..0000000000 --- a/mobile/openapi/lib/model/plugin_filter_response_dto.dart +++ /dev/null @@ -1,158 +0,0 @@ -// -// AUTO-GENERATED FILE, DO NOT MODIFY! -// -// @dart=2.18 - -// ignore_for_file: unused_element, unused_import -// ignore_for_file: always_put_required_named_parameters_first -// ignore_for_file: constant_identifier_names -// ignore_for_file: lines_longer_than_80_chars - -part of openapi.api; - -class PluginFilterResponseDto { - /// Returns a new [PluginFilterResponseDto] instance. - PluginFilterResponseDto({ - required this.description, - required this.id, - required this.methodName, - required this.pluginId, - required this.schema, - this.supportedContexts = const [], - required this.title, - }); - - /// Filter description - String description; - - /// Filter ID - String id; - - /// Method name - String methodName; - - /// Plugin ID - String pluginId; - - /// Filter schema - PluginJsonSchema? schema; - - /// Supported contexts - List supportedContexts; - - /// Filter title - String title; - - @override - bool operator ==(Object other) => identical(this, other) || other is PluginFilterResponseDto && - other.description == description && - other.id == id && - other.methodName == methodName && - other.pluginId == pluginId && - other.schema == schema && - _deepEquality.equals(other.supportedContexts, supportedContexts) && - other.title == title; - - @override - int get hashCode => - // ignore: unnecessary_parenthesis - (description.hashCode) + - (id.hashCode) + - (methodName.hashCode) + - (pluginId.hashCode) + - (schema == null ? 0 : schema!.hashCode) + - (supportedContexts.hashCode) + - (title.hashCode); - - @override - String toString() => 'PluginFilterResponseDto[description=$description, id=$id, methodName=$methodName, pluginId=$pluginId, schema=$schema, supportedContexts=$supportedContexts, title=$title]'; - - Map toJson() { - final json = {}; - json[r'description'] = this.description; - json[r'id'] = this.id; - json[r'methodName'] = this.methodName; - json[r'pluginId'] = this.pluginId; - if (this.schema != null) { - json[r'schema'] = this.schema; - } else { - // json[r'schema'] = null; - } - json[r'supportedContexts'] = this.supportedContexts; - json[r'title'] = this.title; - return json; - } - - /// Returns a new [PluginFilterResponseDto] instance and imports its values from - /// [value] if it's a [Map], null otherwise. - // ignore: prefer_constructors_over_static_methods - static PluginFilterResponseDto? fromJson(dynamic value) { - upgradeDto(value, "PluginFilterResponseDto"); - if (value is Map) { - final json = value.cast(); - - return PluginFilterResponseDto( - description: mapValueOfType(json, r'description')!, - id: mapValueOfType(json, r'id')!, - methodName: mapValueOfType(json, r'methodName')!, - pluginId: mapValueOfType(json, r'pluginId')!, - schema: PluginJsonSchema.fromJson(json[r'schema']), - supportedContexts: PluginContextType.listFromJson(json[r'supportedContexts']), - title: mapValueOfType(json, r'title')!, - ); - } - return null; - } - - static List listFromJson(dynamic json, {bool growable = false,}) { - final result = []; - if (json is List && json.isNotEmpty) { - for (final row in json) { - final value = PluginFilterResponseDto.fromJson(row); - if (value != null) { - result.add(value); - } - } - } - return result.toList(growable: growable); - } - - static Map mapFromJson(dynamic json) { - final map = {}; - if (json is Map && json.isNotEmpty) { - json = json.cast(); // ignore: parameter_assignments - for (final entry in json.entries) { - final value = PluginFilterResponseDto.fromJson(entry.value); - if (value != null) { - map[entry.key] = value; - } - } - } - return map; - } - - // maps a json object with a list of PluginFilterResponseDto-objects as value to a dart map - static Map> mapListFromJson(dynamic json, {bool growable = false,}) { - final map = >{}; - if (json is Map && json.isNotEmpty) { - // ignore: parameter_assignments - json = json.cast(); - for (final entry in json.entries) { - map[entry.key] = PluginFilterResponseDto.listFromJson(entry.value, growable: growable,); - } - } - return map; - } - - /// The list of required keys that must be present in a JSON. - static const requiredKeys = { - 'description', - 'id', - 'methodName', - 'pluginId', - 'schema', - 'supportedContexts', - 'title', - }; -} - diff --git a/mobile/openapi/lib/model/plugin_json_schema.dart b/mobile/openapi/lib/model/plugin_json_schema.dart deleted file mode 100644 index f7a2d584d9..0000000000 --- a/mobile/openapi/lib/model/plugin_json_schema.dart +++ /dev/null @@ -1,158 +0,0 @@ -// -// AUTO-GENERATED FILE, DO NOT MODIFY! -// -// @dart=2.18 - -// ignore_for_file: unused_element, unused_import -// ignore_for_file: always_put_required_named_parameters_first -// ignore_for_file: constant_identifier_names -// ignore_for_file: lines_longer_than_80_chars - -part of openapi.api; - -class PluginJsonSchema { - /// Returns a new [PluginJsonSchema] instance. - PluginJsonSchema({ - this.additionalProperties, - this.description, - this.properties = const {}, - this.required_ = const [], - this.type, - }); - - /// - /// Please note: This property should have been non-nullable! Since the specification file - /// does not include a default value (using the "default:" property), however, the generated - /// source code must fall back to having a nullable type. - /// Consider adding a "default:" property in the specification file to hide this note. - /// - bool? additionalProperties; - - /// - /// Please note: This property should have been non-nullable! Since the specification file - /// does not include a default value (using the "default:" property), however, the generated - /// source code must fall back to having a nullable type. - /// Consider adding a "default:" property in the specification file to hide this note. - /// - String? description; - - Map properties; - - List required_; - - /// - /// Please note: This property should have been non-nullable! Since the specification file - /// does not include a default value (using the "default:" property), however, the generated - /// source code must fall back to having a nullable type. - /// Consider adding a "default:" property in the specification file to hide this note. - /// - PluginJsonSchemaType? type; - - @override - bool operator ==(Object other) => identical(this, other) || other is PluginJsonSchema && - other.additionalProperties == additionalProperties && - other.description == description && - _deepEquality.equals(other.properties, properties) && - _deepEquality.equals(other.required_, required_) && - other.type == type; - - @override - int get hashCode => - // ignore: unnecessary_parenthesis - (additionalProperties == null ? 0 : additionalProperties!.hashCode) + - (description == null ? 0 : description!.hashCode) + - (properties.hashCode) + - (required_.hashCode) + - (type == null ? 0 : type!.hashCode); - - @override - String toString() => 'PluginJsonSchema[additionalProperties=$additionalProperties, description=$description, properties=$properties, required_=$required_, type=$type]'; - - Map toJson() { - final json = {}; - if (this.additionalProperties != null) { - json[r'additionalProperties'] = this.additionalProperties; - } else { - // json[r'additionalProperties'] = null; - } - if (this.description != null) { - json[r'description'] = this.description; - } else { - // json[r'description'] = null; - } - json[r'properties'] = this.properties; - json[r'required'] = this.required_; - if (this.type != null) { - json[r'type'] = this.type; - } else { - // json[r'type'] = null; - } - return json; - } - - /// Returns a new [PluginJsonSchema] instance and imports its values from - /// [value] if it's a [Map], null otherwise. - // ignore: prefer_constructors_over_static_methods - static PluginJsonSchema? fromJson(dynamic value) { - upgradeDto(value, "PluginJsonSchema"); - if (value is Map) { - final json = value.cast(); - - return PluginJsonSchema( - additionalProperties: mapValueOfType(json, r'additionalProperties'), - description: mapValueOfType(json, r'description'), - properties: PluginJsonSchemaProperty.mapFromJson(json[r'properties']), - required_: json[r'required'] is Iterable - ? (json[r'required'] as Iterable).cast().toList(growable: false) - : const [], - type: PluginJsonSchemaType.fromJson(json[r'type']), - ); - } - return null; - } - - static List listFromJson(dynamic json, {bool growable = false,}) { - final result = []; - if (json is List && json.isNotEmpty) { - for (final row in json) { - final value = PluginJsonSchema.fromJson(row); - if (value != null) { - result.add(value); - } - } - } - return result.toList(growable: growable); - } - - static Map mapFromJson(dynamic json) { - final map = {}; - if (json is Map && json.isNotEmpty) { - json = json.cast(); // ignore: parameter_assignments - for (final entry in json.entries) { - final value = PluginJsonSchema.fromJson(entry.value); - if (value != null) { - map[entry.key] = value; - } - } - } - return map; - } - - // maps a json object with a list of PluginJsonSchema-objects as value to a dart map - static Map> mapListFromJson(dynamic json, {bool growable = false,}) { - final map = >{}; - if (json is Map && json.isNotEmpty) { - // ignore: parameter_assignments - json = json.cast(); - for (final entry in json.entries) { - map[entry.key] = PluginJsonSchema.listFromJson(entry.value, growable: growable,); - } - } - return map; - } - - /// The list of required keys that must be present in a JSON. - static const requiredKeys = { - }; -} - diff --git a/mobile/openapi/lib/model/plugin_json_schema_property.dart b/mobile/openapi/lib/model/plugin_json_schema_property.dart deleted file mode 100644 index 65951da0a3..0000000000 --- a/mobile/openapi/lib/model/plugin_json_schema_property.dart +++ /dev/null @@ -1,195 +0,0 @@ -// -// AUTO-GENERATED FILE, DO NOT MODIFY! -// -// @dart=2.18 - -// ignore_for_file: unused_element, unused_import -// ignore_for_file: always_put_required_named_parameters_first -// ignore_for_file: constant_identifier_names -// ignore_for_file: lines_longer_than_80_chars - -part of openapi.api; - -class PluginJsonSchemaProperty { - /// Returns a new [PluginJsonSchemaProperty] instance. - PluginJsonSchemaProperty({ - this.additionalProperties, - this.default_, - this.description, - this.enum_ = const [], - this.items, - this.properties = const {}, - this.required_ = const [], - this.type, - }); - - /// - /// Please note: This property should have been non-nullable! Since the specification file - /// does not include a default value (using the "default:" property), however, the generated - /// source code must fall back to having a nullable type. - /// Consider adding a "default:" property in the specification file to hide this note. - /// - PluginJsonSchemaPropertyAdditionalProperties? additionalProperties; - - Object? default_; - - /// - /// Please note: This property should have been non-nullable! Since the specification file - /// does not include a default value (using the "default:" property), however, the generated - /// source code must fall back to having a nullable type. - /// Consider adding a "default:" property in the specification file to hide this note. - /// - String? description; - - List enum_; - - /// - /// Please note: This property should have been non-nullable! Since the specification file - /// does not include a default value (using the "default:" property), however, the generated - /// source code must fall back to having a nullable type. - /// Consider adding a "default:" property in the specification file to hide this note. - /// - PluginJsonSchemaProperty? items; - - Map properties; - - List required_; - - /// - /// Please note: This property should have been non-nullable! Since the specification file - /// does not include a default value (using the "default:" property), however, the generated - /// source code must fall back to having a nullable type. - /// Consider adding a "default:" property in the specification file to hide this note. - /// - PluginJsonSchemaType? type; - - @override - bool operator ==(Object other) => identical(this, other) || other is PluginJsonSchemaProperty && - other.additionalProperties == additionalProperties && - other.default_ == default_ && - other.description == description && - _deepEquality.equals(other.enum_, enum_) && - other.items == items && - _deepEquality.equals(other.properties, properties) && - _deepEquality.equals(other.required_, required_) && - other.type == type; - - @override - int get hashCode => - // ignore: unnecessary_parenthesis - (additionalProperties == null ? 0 : additionalProperties!.hashCode) + - (default_ == null ? 0 : default_!.hashCode) + - (description == null ? 0 : description!.hashCode) + - (enum_.hashCode) + - (items == null ? 0 : items!.hashCode) + - (properties.hashCode) + - (required_.hashCode) + - (type == null ? 0 : type!.hashCode); - - @override - String toString() => 'PluginJsonSchemaProperty[additionalProperties=$additionalProperties, default_=$default_, description=$description, enum_=$enum_, items=$items, properties=$properties, required_=$required_, type=$type]'; - - Map toJson() { - final json = {}; - if (this.additionalProperties != null) { - json[r'additionalProperties'] = this.additionalProperties; - } else { - // json[r'additionalProperties'] = null; - } - if (this.default_ != null) { - json[r'default'] = this.default_; - } else { - // json[r'default'] = null; - } - if (this.description != null) { - json[r'description'] = this.description; - } else { - // json[r'description'] = null; - } - json[r'enum'] = this.enum_; - if (this.items != null) { - json[r'items'] = this.items; - } else { - // json[r'items'] = null; - } - json[r'properties'] = this.properties; - json[r'required'] = this.required_; - if (this.type != null) { - json[r'type'] = this.type; - } else { - // json[r'type'] = null; - } - return json; - } - - /// Returns a new [PluginJsonSchemaProperty] instance and imports its values from - /// [value] if it's a [Map], null otherwise. - // ignore: prefer_constructors_over_static_methods - static PluginJsonSchemaProperty? fromJson(dynamic value) { - upgradeDto(value, "PluginJsonSchemaProperty"); - if (value is Map) { - final json = value.cast(); - - return PluginJsonSchemaProperty( - additionalProperties: PluginJsonSchemaPropertyAdditionalProperties.fromJson(json[r'additionalProperties']), - default_: mapValueOfType(json, r'default'), - description: mapValueOfType(json, r'description'), - enum_: json[r'enum'] is Iterable - ? (json[r'enum'] as Iterable).cast().toList(growable: false) - : const [], - items: PluginJsonSchemaProperty.fromJson(json[r'items']), - properties: PluginJsonSchemaProperty.mapFromJson(json[r'properties']), - required_: json[r'required'] is Iterable - ? (json[r'required'] as Iterable).cast().toList(growable: false) - : const [], - type: PluginJsonSchemaType.fromJson(json[r'type']), - ); - } - return null; - } - - static List listFromJson(dynamic json, {bool growable = false,}) { - final result = []; - if (json is List && json.isNotEmpty) { - for (final row in json) { - final value = PluginJsonSchemaProperty.fromJson(row); - if (value != null) { - result.add(value); - } - } - } - return result.toList(growable: growable); - } - - static Map mapFromJson(dynamic json) { - final map = {}; - if (json is Map && json.isNotEmpty) { - json = json.cast(); // ignore: parameter_assignments - for (final entry in json.entries) { - final value = PluginJsonSchemaProperty.fromJson(entry.value); - if (value != null) { - map[entry.key] = value; - } - } - } - return map; - } - - // maps a json object with a list of PluginJsonSchemaProperty-objects as value to a dart map - static Map> mapListFromJson(dynamic json, {bool growable = false,}) { - final map = >{}; - if (json is Map && json.isNotEmpty) { - // ignore: parameter_assignments - json = json.cast(); - for (final entry in json.entries) { - map[entry.key] = PluginJsonSchemaProperty.listFromJson(entry.value, growable: growable,); - } - } - return map; - } - - /// The list of required keys that must be present in a JSON. - static const requiredKeys = { - }; -} - diff --git a/mobile/openapi/lib/model/plugin_json_schema_property_additional_properties.dart b/mobile/openapi/lib/model/plugin_json_schema_property_additional_properties.dart deleted file mode 100644 index 169c6be772..0000000000 --- a/mobile/openapi/lib/model/plugin_json_schema_property_additional_properties.dart +++ /dev/null @@ -1,195 +0,0 @@ -// -// AUTO-GENERATED FILE, DO NOT MODIFY! -// -// @dart=2.18 - -// ignore_for_file: unused_element, unused_import -// ignore_for_file: always_put_required_named_parameters_first -// ignore_for_file: constant_identifier_names -// ignore_for_file: lines_longer_than_80_chars - -part of openapi.api; - -class PluginJsonSchemaPropertyAdditionalProperties { - /// Returns a new [PluginJsonSchemaPropertyAdditionalProperties] instance. - PluginJsonSchemaPropertyAdditionalProperties({ - this.additionalProperties, - this.default_, - this.description, - this.enum_ = const [], - this.items, - this.properties = const {}, - this.required_ = const [], - this.type, - }); - - /// - /// Please note: This property should have been non-nullable! Since the specification file - /// does not include a default value (using the "default:" property), however, the generated - /// source code must fall back to having a nullable type. - /// Consider adding a "default:" property in the specification file to hide this note. - /// - PluginJsonSchemaPropertyAdditionalProperties? additionalProperties; - - Object? default_; - - /// - /// Please note: This property should have been non-nullable! Since the specification file - /// does not include a default value (using the "default:" property), however, the generated - /// source code must fall back to having a nullable type. - /// Consider adding a "default:" property in the specification file to hide this note. - /// - String? description; - - List enum_; - - /// - /// Please note: This property should have been non-nullable! Since the specification file - /// does not include a default value (using the "default:" property), however, the generated - /// source code must fall back to having a nullable type. - /// Consider adding a "default:" property in the specification file to hide this note. - /// - PluginJsonSchemaProperty? items; - - Map properties; - - List required_; - - /// - /// Please note: This property should have been non-nullable! Since the specification file - /// does not include a default value (using the "default:" property), however, the generated - /// source code must fall back to having a nullable type. - /// Consider adding a "default:" property in the specification file to hide this note. - /// - PluginJsonSchemaType? type; - - @override - bool operator ==(Object other) => identical(this, other) || other is PluginJsonSchemaPropertyAdditionalProperties && - other.additionalProperties == additionalProperties && - other.default_ == default_ && - other.description == description && - _deepEquality.equals(other.enum_, enum_) && - other.items == items && - _deepEquality.equals(other.properties, properties) && - _deepEquality.equals(other.required_, required_) && - other.type == type; - - @override - int get hashCode => - // ignore: unnecessary_parenthesis - (additionalProperties == null ? 0 : additionalProperties!.hashCode) + - (default_ == null ? 0 : default_!.hashCode) + - (description == null ? 0 : description!.hashCode) + - (enum_.hashCode) + - (items == null ? 0 : items!.hashCode) + - (properties.hashCode) + - (required_.hashCode) + - (type == null ? 0 : type!.hashCode); - - @override - String toString() => 'PluginJsonSchemaPropertyAdditionalProperties[additionalProperties=$additionalProperties, default_=$default_, description=$description, enum_=$enum_, items=$items, properties=$properties, required_=$required_, type=$type]'; - - Map toJson() { - final json = {}; - if (this.additionalProperties != null) { - json[r'additionalProperties'] = this.additionalProperties; - } else { - // json[r'additionalProperties'] = null; - } - if (this.default_ != null) { - json[r'default'] = this.default_; - } else { - // json[r'default'] = null; - } - if (this.description != null) { - json[r'description'] = this.description; - } else { - // json[r'description'] = null; - } - json[r'enum'] = this.enum_; - if (this.items != null) { - json[r'items'] = this.items; - } else { - // json[r'items'] = null; - } - json[r'properties'] = this.properties; - json[r'required'] = this.required_; - if (this.type != null) { - json[r'type'] = this.type; - } else { - // json[r'type'] = null; - } - return json; - } - - /// Returns a new [PluginJsonSchemaPropertyAdditionalProperties] instance and imports its values from - /// [value] if it's a [Map], null otherwise. - // ignore: prefer_constructors_over_static_methods - static PluginJsonSchemaPropertyAdditionalProperties? fromJson(dynamic value) { - upgradeDto(value, "PluginJsonSchemaPropertyAdditionalProperties"); - if (value is Map) { - final json = value.cast(); - - return PluginJsonSchemaPropertyAdditionalProperties( - additionalProperties: PluginJsonSchemaPropertyAdditionalProperties.fromJson(json[r'additionalProperties']), - default_: mapValueOfType(json, r'default'), - description: mapValueOfType(json, r'description'), - enum_: json[r'enum'] is Iterable - ? (json[r'enum'] as Iterable).cast().toList(growable: false) - : const [], - items: PluginJsonSchemaProperty.fromJson(json[r'items']), - properties: PluginJsonSchemaProperty.mapFromJson(json[r'properties']), - required_: json[r'required'] is Iterable - ? (json[r'required'] as Iterable).cast().toList(growable: false) - : const [], - type: PluginJsonSchemaType.fromJson(json[r'type']), - ); - } - return null; - } - - static List listFromJson(dynamic json, {bool growable = false,}) { - final result = []; - if (json is List && json.isNotEmpty) { - for (final row in json) { - final value = PluginJsonSchemaPropertyAdditionalProperties.fromJson(row); - if (value != null) { - result.add(value); - } - } - } - return result.toList(growable: growable); - } - - static Map mapFromJson(dynamic json) { - final map = {}; - if (json is Map && json.isNotEmpty) { - json = json.cast(); // ignore: parameter_assignments - for (final entry in json.entries) { - final value = PluginJsonSchemaPropertyAdditionalProperties.fromJson(entry.value); - if (value != null) { - map[entry.key] = value; - } - } - } - return map; - } - - // maps a json object with a list of PluginJsonSchemaPropertyAdditionalProperties-objects as value to a dart map - static Map> mapListFromJson(dynamic json, {bool growable = false,}) { - final map = >{}; - if (json is Map && json.isNotEmpty) { - // ignore: parameter_assignments - json = json.cast(); - for (final entry in json.entries) { - map[entry.key] = PluginJsonSchemaPropertyAdditionalProperties.listFromJson(entry.value, growable: growable,); - } - } - return map; - } - - /// The list of required keys that must be present in a JSON. - static const requiredKeys = { - }; -} - diff --git a/mobile/openapi/lib/model/plugin_json_schema_type.dart b/mobile/openapi/lib/model/plugin_json_schema_type.dart deleted file mode 100644 index cabac9b71b..0000000000 --- a/mobile/openapi/lib/model/plugin_json_schema_type.dart +++ /dev/null @@ -1,100 +0,0 @@ -// -// AUTO-GENERATED FILE, DO NOT MODIFY! -// -// @dart=2.18 - -// ignore_for_file: unused_element, unused_import -// ignore_for_file: always_put_required_named_parameters_first -// ignore_for_file: constant_identifier_names -// ignore_for_file: lines_longer_than_80_chars - -part of openapi.api; - - -class PluginJsonSchemaType { - /// Instantiate a new enum with the provided [value]. - const PluginJsonSchemaType._(this.value); - - /// The underlying value of this enum member. - final String value; - - @override - String toString() => value; - - String toJson() => value; - - static const string = PluginJsonSchemaType._(r'string'); - static const number = PluginJsonSchemaType._(r'number'); - static const integer = PluginJsonSchemaType._(r'integer'); - static const boolean = PluginJsonSchemaType._(r'boolean'); - static const object = PluginJsonSchemaType._(r'object'); - static const array = PluginJsonSchemaType._(r'array'); - static const null_ = PluginJsonSchemaType._(r'null'); - - /// List of all possible values in this [enum][PluginJsonSchemaType]. - static const values = [ - string, - number, - integer, - boolean, - object, - array, - null_, - ]; - - static PluginJsonSchemaType? fromJson(dynamic value) => PluginJsonSchemaTypeTypeTransformer().decode(value); - - static List listFromJson(dynamic json, {bool growable = false,}) { - final result = []; - if (json is List && json.isNotEmpty) { - for (final row in json) { - final value = PluginJsonSchemaType.fromJson(row); - if (value != null) { - result.add(value); - } - } - } - return result.toList(growable: growable); - } -} - -/// Transformation class that can [encode] an instance of [PluginJsonSchemaType] to String, -/// and [decode] dynamic data back to [PluginJsonSchemaType]. -class PluginJsonSchemaTypeTypeTransformer { - factory PluginJsonSchemaTypeTypeTransformer() => _instance ??= const PluginJsonSchemaTypeTypeTransformer._(); - - const PluginJsonSchemaTypeTypeTransformer._(); - - String encode(PluginJsonSchemaType data) => data.value; - - /// Decodes a [dynamic value][data] to a PluginJsonSchemaType. - /// - /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, - /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] - /// cannot be decoded successfully, then an [UnimplementedError] is thrown. - /// - /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, - /// and users are still using an old app with the old code. - PluginJsonSchemaType? decode(dynamic data, {bool allowNull = true}) { - if (data != null) { - switch (data) { - case r'string': return PluginJsonSchemaType.string; - case r'number': return PluginJsonSchemaType.number; - case r'integer': return PluginJsonSchemaType.integer; - case r'boolean': return PluginJsonSchemaType.boolean; - case r'object': return PluginJsonSchemaType.object; - case r'array': return PluginJsonSchemaType.array; - case r'null': return PluginJsonSchemaType.null_; - default: - if (!allowNull) { - throw ArgumentError('Unknown enum value to decode: $data'); - } - } - } - return null; - } - - /// Singleton [PluginJsonSchemaTypeTypeTransformer] instance. - static PluginJsonSchemaTypeTypeTransformer? _instance; -} - diff --git a/mobile/openapi/lib/model/plugin_method_response_dto.dart b/mobile/openapi/lib/model/plugin_method_response_dto.dart new file mode 100644 index 0000000000..1d6f9c1331 --- /dev/null +++ b/mobile/openapi/lib/model/plugin_method_response_dto.dart @@ -0,0 +1,171 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class PluginMethodResponseDto { + /// Returns a new [PluginMethodResponseDto] instance. + PluginMethodResponseDto({ + required this.description, + required this.hostFunctions, + required this.key, + required this.name, + this.schema = const Optional.absent(), + required this.title, + this.types = const [], + this.uiHints = const [], + }); + + /// Description + String description; + + bool hostFunctions; + + /// Key + String key; + + /// Name + String name; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + Optional schema; + + /// Title + String title; + + /// Workflow types + List types; + + /// Ui hints + List uiHints; + + @override + bool operator ==(Object other) => identical(this, other) || other is PluginMethodResponseDto && + other.description == description && + other.hostFunctions == hostFunctions && + other.key == key && + other.name == name && + other.schema == schema && + other.title == title && + _deepEquality.equals(other.types, types) && + _deepEquality.equals(other.uiHints, uiHints); + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (description.hashCode) + + (hostFunctions.hashCode) + + (key.hashCode) + + (name.hashCode) + + (schema == null ? 0 : schema!.hashCode) + + (title.hashCode) + + (types.hashCode) + + (uiHints.hashCode); + + @override + String toString() => 'PluginMethodResponseDto[description=$description, hostFunctions=$hostFunctions, key=$key, name=$name, schema=$schema, title=$title, types=$types, uiHints=$uiHints]'; + + Map toJson() { + final json = {}; + json[r'description'] = this.description; + json[r'hostFunctions'] = this.hostFunctions; + json[r'key'] = this.key; + json[r'name'] = this.name; + if (this.schema.isPresent) { + final value = this.schema.value; + json[r'schema'] = value; + } + json[r'title'] = this.title; + json[r'types'] = this.types; + json[r'uiHints'] = this.uiHints; + return json; + } + + /// Returns a new [PluginMethodResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static PluginMethodResponseDto? fromJson(dynamic value) { + upgradeDto(value, "PluginMethodResponseDto"); + if (value is Map) { + final json = value.cast(); + + return PluginMethodResponseDto( + description: mapValueOfType(json, r'description')!, + hostFunctions: mapValueOfType(json, r'hostFunctions')!, + key: mapValueOfType(json, r'key')!, + name: mapValueOfType(json, r'name')!, + schema: json.containsKey(r'schema') ? Optional.present(mapValueOfType(json, r'schema')) : const Optional.absent(), + title: mapValueOfType(json, r'title')!, + types: WorkflowType.listFromJson(json[r'types']), + uiHints: json[r'uiHints'] is Iterable + ? (json[r'uiHints'] as Iterable).cast().toList(growable: false) + : const [], + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = PluginMethodResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = PluginMethodResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of PluginMethodResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = PluginMethodResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'description', + 'hostFunctions', + 'key', + 'name', + 'title', + 'types', + 'uiHints', + }; +} + diff --git a/mobile/openapi/lib/model/plugin_response_dto.dart b/mobile/openapi/lib/model/plugin_response_dto.dart index 7a99896475..1bdb366f9e 100644 --- a/mobile/openapi/lib/model/plugin_response_dto.dart +++ b/mobile/openapi/lib/model/plugin_response_dto.dart @@ -13,21 +13,17 @@ part of openapi.api; class PluginResponseDto { /// Returns a new [PluginResponseDto] instance. PluginResponseDto({ - this.actions = const [], required this.author, required this.createdAt, required this.description, - this.filters = const [], required this.id, + this.methods = const [], required this.name, required this.title, required this.updatedAt, required this.version, }); - /// Plugin actions - List actions; - /// Plugin author String author; @@ -37,12 +33,12 @@ class PluginResponseDto { /// Plugin description String description; - /// Plugin filters - List filters; - /// Plugin ID String id; + /// Plugin methods + List methods; + /// Plugin name String name; @@ -57,12 +53,11 @@ class PluginResponseDto { @override bool operator ==(Object other) => identical(this, other) || other is PluginResponseDto && - _deepEquality.equals(other.actions, actions) && other.author == author && other.createdAt == createdAt && other.description == description && - _deepEquality.equals(other.filters, filters) && other.id == id && + _deepEquality.equals(other.methods, methods) && other.name == name && other.title == title && other.updatedAt == updatedAt && @@ -71,28 +66,26 @@ class PluginResponseDto { @override int get hashCode => // ignore: unnecessary_parenthesis - (actions.hashCode) + (author.hashCode) + (createdAt.hashCode) + (description.hashCode) + - (filters.hashCode) + (id.hashCode) + + (methods.hashCode) + (name.hashCode) + (title.hashCode) + (updatedAt.hashCode) + (version.hashCode); @override - String toString() => 'PluginResponseDto[actions=$actions, author=$author, createdAt=$createdAt, description=$description, filters=$filters, id=$id, name=$name, title=$title, updatedAt=$updatedAt, version=$version]'; + String toString() => 'PluginResponseDto[author=$author, createdAt=$createdAt, description=$description, id=$id, methods=$methods, name=$name, title=$title, updatedAt=$updatedAt, version=$version]'; Map toJson() { final json = {}; - json[r'actions'] = this.actions; json[r'author'] = this.author; json[r'createdAt'] = this.createdAt; json[r'description'] = this.description; - json[r'filters'] = this.filters; json[r'id'] = this.id; + json[r'methods'] = this.methods; json[r'name'] = this.name; json[r'title'] = this.title; json[r'updatedAt'] = this.updatedAt; @@ -109,12 +102,11 @@ class PluginResponseDto { final json = value.cast(); return PluginResponseDto( - actions: PluginActionResponseDto.listFromJson(json[r'actions']), author: mapValueOfType(json, r'author')!, createdAt: mapValueOfType(json, r'createdAt')!, description: mapValueOfType(json, r'description')!, - filters: PluginFilterResponseDto.listFromJson(json[r'filters']), id: mapValueOfType(json, r'id')!, + methods: PluginMethodResponseDto.listFromJson(json[r'methods']), name: mapValueOfType(json, r'name')!, title: mapValueOfType(json, r'title')!, updatedAt: mapValueOfType(json, r'updatedAt')!, @@ -166,12 +158,11 @@ class PluginResponseDto { /// The list of required keys that must be present in a JSON. static const requiredKeys = { - 'actions', 'author', 'createdAt', 'description', - 'filters', 'id', + 'methods', 'name', 'title', 'updatedAt', diff --git a/mobile/openapi/lib/model/plugin_template_response_dto.dart b/mobile/openapi/lib/model/plugin_template_response_dto.dart new file mode 100644 index 0000000000..9f54753f49 --- /dev/null +++ b/mobile/openapi/lib/model/plugin_template_response_dto.dart @@ -0,0 +1,146 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class PluginTemplateResponseDto { + /// Returns a new [PluginTemplateResponseDto] instance. + PluginTemplateResponseDto({ + required this.description, + required this.key, + this.steps = const [], + required this.title, + required this.trigger, + this.uiHints = const [], + }); + + /// Template description + String description; + + /// Template key (unique across all templates) + String key; + + /// Workflow steps + List steps; + + /// Template title + String title; + + WorkflowTrigger trigger; + + /// Ui hints, for example \"smart-album\" + List uiHints; + + @override + bool operator ==(Object other) => identical(this, other) || other is PluginTemplateResponseDto && + other.description == description && + other.key == key && + _deepEquality.equals(other.steps, steps) && + other.title == title && + other.trigger == trigger && + _deepEquality.equals(other.uiHints, uiHints); + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (description.hashCode) + + (key.hashCode) + + (steps.hashCode) + + (title.hashCode) + + (trigger.hashCode) + + (uiHints.hashCode); + + @override + String toString() => 'PluginTemplateResponseDto[description=$description, key=$key, steps=$steps, title=$title, trigger=$trigger, uiHints=$uiHints]'; + + Map toJson() { + final json = {}; + json[r'description'] = this.description; + json[r'key'] = this.key; + json[r'steps'] = this.steps; + json[r'title'] = this.title; + json[r'trigger'] = this.trigger; + json[r'uiHints'] = this.uiHints; + return json; + } + + /// Returns a new [PluginTemplateResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static PluginTemplateResponseDto? fromJson(dynamic value) { + upgradeDto(value, "PluginTemplateResponseDto"); + if (value is Map) { + final json = value.cast(); + + return PluginTemplateResponseDto( + description: mapValueOfType(json, r'description')!, + key: mapValueOfType(json, r'key')!, + steps: PluginTemplateStepResponseDto.listFromJson(json[r'steps']), + title: mapValueOfType(json, r'title')!, + trigger: WorkflowTrigger.fromJson(json[r'trigger'])!, + uiHints: json[r'uiHints'] is Iterable + ? (json[r'uiHints'] as Iterable).cast().toList(growable: false) + : const [], + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = PluginTemplateResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = PluginTemplateResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of PluginTemplateResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = PluginTemplateResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'description', + 'key', + 'steps', + 'title', + 'trigger', + 'uiHints', + }; +} + diff --git a/mobile/openapi/lib/model/plugin_template_step_response_dto.dart b/mobile/openapi/lib/model/plugin_template_step_response_dto.dart new file mode 100644 index 0000000000..3e82c029d2 --- /dev/null +++ b/mobile/openapi/lib/model/plugin_template_step_response_dto.dart @@ -0,0 +1,130 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class PluginTemplateStepResponseDto { + /// Returns a new [PluginTemplateStepResponseDto] instance. + PluginTemplateStepResponseDto({ + this.config = const {}, + this.enabled = const Optional.absent(), + required this.method, + }); + + /// Step configuration + Map? config; + + /// Whether the step is enabled + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + Optional enabled; + + /// Step plugin method + String method; + + @override + bool operator ==(Object other) => identical(this, other) || other is PluginTemplateStepResponseDto && + _deepEquality.equals(other.config, config) && + other.enabled == enabled && + other.method == method; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (config == null ? 0 : config!.hashCode) + + (enabled == null ? 0 : enabled!.hashCode) + + (method.hashCode); + + @override + String toString() => 'PluginTemplateStepResponseDto[config=$config, enabled=$enabled, method=$method]'; + + Map toJson() { + final json = {}; + if (this.config != null) { + json[r'config'] = this.config; + } else { + json[r'config'] = null; + } + if (this.enabled.isPresent) { + final value = this.enabled.value; + json[r'enabled'] = value; + } + json[r'method'] = this.method; + return json; + } + + /// Returns a new [PluginTemplateStepResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static PluginTemplateStepResponseDto? fromJson(dynamic value) { + upgradeDto(value, "PluginTemplateStepResponseDto"); + if (value is Map) { + final json = value.cast(); + + return PluginTemplateStepResponseDto( + config: mapCastOfType(json, r'config'), + enabled: json.containsKey(r'enabled') ? Optional.present(mapValueOfType(json, r'enabled')) : const Optional.absent(), + method: mapValueOfType(json, r'method')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = PluginTemplateStepResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = PluginTemplateStepResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of PluginTemplateStepResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = PluginTemplateStepResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'config', + 'method', + }; +} + diff --git a/mobile/openapi/lib/model/plugin_trigger_response_dto.dart b/mobile/openapi/lib/model/plugin_trigger_response_dto.dart deleted file mode 100644 index a6ee1c6b69..0000000000 --- a/mobile/openapi/lib/model/plugin_trigger_response_dto.dart +++ /dev/null @@ -1,107 +0,0 @@ -// -// AUTO-GENERATED FILE, DO NOT MODIFY! -// -// @dart=2.18 - -// ignore_for_file: unused_element, unused_import -// ignore_for_file: always_put_required_named_parameters_first -// ignore_for_file: constant_identifier_names -// ignore_for_file: lines_longer_than_80_chars - -part of openapi.api; - -class PluginTriggerResponseDto { - /// Returns a new [PluginTriggerResponseDto] instance. - PluginTriggerResponseDto({ - required this.contextType, - required this.type, - }); - - PluginContextType contextType; - - PluginTriggerType type; - - @override - bool operator ==(Object other) => identical(this, other) || other is PluginTriggerResponseDto && - other.contextType == contextType && - other.type == type; - - @override - int get hashCode => - // ignore: unnecessary_parenthesis - (contextType.hashCode) + - (type.hashCode); - - @override - String toString() => 'PluginTriggerResponseDto[contextType=$contextType, type=$type]'; - - Map toJson() { - final json = {}; - json[r'contextType'] = this.contextType; - json[r'type'] = this.type; - return json; - } - - /// Returns a new [PluginTriggerResponseDto] instance and imports its values from - /// [value] if it's a [Map], null otherwise. - // ignore: prefer_constructors_over_static_methods - static PluginTriggerResponseDto? fromJson(dynamic value) { - upgradeDto(value, "PluginTriggerResponseDto"); - if (value is Map) { - final json = value.cast(); - - return PluginTriggerResponseDto( - contextType: PluginContextType.fromJson(json[r'contextType'])!, - type: PluginTriggerType.fromJson(json[r'type'])!, - ); - } - return null; - } - - static List listFromJson(dynamic json, {bool growable = false,}) { - final result = []; - if (json is List && json.isNotEmpty) { - for (final row in json) { - final value = PluginTriggerResponseDto.fromJson(row); - if (value != null) { - result.add(value); - } - } - } - return result.toList(growable: growable); - } - - static Map mapFromJson(dynamic json) { - final map = {}; - if (json is Map && json.isNotEmpty) { - json = json.cast(); // ignore: parameter_assignments - for (final entry in json.entries) { - final value = PluginTriggerResponseDto.fromJson(entry.value); - if (value != null) { - map[entry.key] = value; - } - } - } - return map; - } - - // maps a json object with a list of PluginTriggerResponseDto-objects as value to a dart map - static Map> mapListFromJson(dynamic json, {bool growable = false,}) { - final map = >{}; - if (json is Map && json.isNotEmpty) { - // ignore: parameter_assignments - json = json.cast(); - for (final entry in json.entries) { - map[entry.key] = PluginTriggerResponseDto.listFromJson(entry.value, growable: growable,); - } - } - return map; - } - - /// The list of required keys that must be present in a JSON. - static const requiredKeys = { - 'contextType', - 'type', - }; -} - diff --git a/mobile/openapi/lib/model/plugin_trigger_type.dart b/mobile/openapi/lib/model/plugin_trigger_type.dart deleted file mode 100644 index 3ebcef7a95..0000000000 --- a/mobile/openapi/lib/model/plugin_trigger_type.dart +++ /dev/null @@ -1,85 +0,0 @@ -// -// AUTO-GENERATED FILE, DO NOT MODIFY! -// -// @dart=2.18 - -// ignore_for_file: unused_element, unused_import -// ignore_for_file: always_put_required_named_parameters_first -// ignore_for_file: constant_identifier_names -// ignore_for_file: lines_longer_than_80_chars - -part of openapi.api; - -/// Plugin trigger type -class PluginTriggerType { - /// Instantiate a new enum with the provided [value]. - const PluginTriggerType._(this.value); - - /// The underlying value of this enum member. - final String value; - - @override - String toString() => value; - - String toJson() => value; - - static const assetCreate = PluginTriggerType._(r'AssetCreate'); - static const personRecognized = PluginTriggerType._(r'PersonRecognized'); - - /// List of all possible values in this [enum][PluginTriggerType]. - static const values = [ - assetCreate, - personRecognized, - ]; - - static PluginTriggerType? fromJson(dynamic value) => PluginTriggerTypeTypeTransformer().decode(value); - - static List listFromJson(dynamic json, {bool growable = false,}) { - final result = []; - if (json is List && json.isNotEmpty) { - for (final row in json) { - final value = PluginTriggerType.fromJson(row); - if (value != null) { - result.add(value); - } - } - } - return result.toList(growable: growable); - } -} - -/// Transformation class that can [encode] an instance of [PluginTriggerType] to String, -/// and [decode] dynamic data back to [PluginTriggerType]. -class PluginTriggerTypeTypeTransformer { - factory PluginTriggerTypeTypeTransformer() => _instance ??= const PluginTriggerTypeTypeTransformer._(); - - const PluginTriggerTypeTypeTransformer._(); - - String encode(PluginTriggerType data) => data.value; - - /// Decodes a [dynamic value][data] to a PluginTriggerType. - /// - /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, - /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] - /// cannot be decoded successfully, then an [UnimplementedError] is thrown. - /// - /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, - /// and users are still using an old app with the old code. - PluginTriggerType? decode(dynamic data, {bool allowNull = true}) { - if (data != null) { - switch (data) { - case r'AssetCreate': return PluginTriggerType.assetCreate; - case r'PersonRecognized': return PluginTriggerType.personRecognized; - default: - if (!allowNull) { - throw ArgumentError('Unknown enum value to decode: $data'); - } - } - } - return null; - } - - /// Singleton [PluginTriggerTypeTypeTransformer] instance. - static PluginTriggerTypeTypeTransformer? _instance; -} - diff --git a/mobile/openapi/lib/model/purchase_update.dart b/mobile/openapi/lib/model/purchase_update.dart index 913faf9bc4..8a0fb0d1b6 100644 --- a/mobile/openapi/lib/model/purchase_update.dart +++ b/mobile/openapi/lib/model/purchase_update.dart @@ -13,8 +13,8 @@ part of openapi.api; class PurchaseUpdate { /// Returns a new [PurchaseUpdate] instance. PurchaseUpdate({ - this.hideBuyButtonUntil, - this.showSupportBadge, + this.hideBuyButtonUntil = const Optional.absent(), + this.showSupportBadge = const Optional.absent(), }); /// Date until which to hide buy button @@ -24,7 +24,7 @@ class PurchaseUpdate { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? hideBuyButtonUntil; + Optional hideBuyButtonUntil; /// Whether to show support badge /// @@ -33,7 +33,7 @@ class PurchaseUpdate { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? showSupportBadge; + Optional showSupportBadge; @override bool operator ==(Object other) => identical(this, other) || other is PurchaseUpdate && @@ -51,15 +51,13 @@ class PurchaseUpdate { Map toJson() { final json = {}; - if (this.hideBuyButtonUntil != null) { - json[r'hideBuyButtonUntil'] = this.hideBuyButtonUntil; - } else { - // json[r'hideBuyButtonUntil'] = null; + if (this.hideBuyButtonUntil.isPresent) { + final value = this.hideBuyButtonUntil.value; + json[r'hideBuyButtonUntil'] = value; } - if (this.showSupportBadge != null) { - json[r'showSupportBadge'] = this.showSupportBadge; - } else { - // json[r'showSupportBadge'] = null; + if (this.showSupportBadge.isPresent) { + final value = this.showSupportBadge.value; + json[r'showSupportBadge'] = value; } return json; } @@ -73,8 +71,8 @@ class PurchaseUpdate { final json = value.cast(); return PurchaseUpdate( - hideBuyButtonUntil: mapValueOfType(json, r'hideBuyButtonUntil'), - showSupportBadge: mapValueOfType(json, r'showSupportBadge'), + hideBuyButtonUntil: json.containsKey(r'hideBuyButtonUntil') ? Optional.present(mapValueOfType(json, r'hideBuyButtonUntil')) : const Optional.absent(), + showSupportBadge: json.containsKey(r'showSupportBadge') ? Optional.present(mapValueOfType(json, r'showSupportBadge')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/queue_command_dto.dart b/mobile/openapi/lib/model/queue_command_dto.dart index fb68d85583..e8a600923f 100644 --- a/mobile/openapi/lib/model/queue_command_dto.dart +++ b/mobile/openapi/lib/model/queue_command_dto.dart @@ -14,7 +14,7 @@ class QueueCommandDto { /// Returns a new [QueueCommandDto] instance. QueueCommandDto({ required this.command, - this.force, + this.force = const Optional.absent(), }); QueueCommand command; @@ -26,7 +26,7 @@ class QueueCommandDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? force; + Optional force; @override bool operator ==(Object other) => identical(this, other) || other is QueueCommandDto && @@ -45,10 +45,9 @@ class QueueCommandDto { Map toJson() { final json = {}; json[r'command'] = this.command; - if (this.force != null) { - json[r'force'] = this.force; - } else { - // json[r'force'] = null; + if (this.force.isPresent) { + final value = this.force.value; + json[r'force'] = value; } return json; } @@ -63,7 +62,7 @@ class QueueCommandDto { return QueueCommandDto( command: QueueCommand.fromJson(json[r'command'])!, - force: mapValueOfType(json, r'force'), + force: json.containsKey(r'force') ? Optional.present(mapValueOfType(json, r'force')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/queue_delete_dto.dart b/mobile/openapi/lib/model/queue_delete_dto.dart index d319238f92..9511313f01 100644 --- a/mobile/openapi/lib/model/queue_delete_dto.dart +++ b/mobile/openapi/lib/model/queue_delete_dto.dart @@ -13,7 +13,7 @@ part of openapi.api; class QueueDeleteDto { /// Returns a new [QueueDeleteDto] instance. QueueDeleteDto({ - this.failed, + this.failed = const Optional.absent(), }); /// If true, will also remove failed jobs from the queue. @@ -23,7 +23,7 @@ class QueueDeleteDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? failed; + Optional failed; @override bool operator ==(Object other) => identical(this, other) || other is QueueDeleteDto && @@ -39,10 +39,9 @@ class QueueDeleteDto { Map toJson() { final json = {}; - if (this.failed != null) { - json[r'failed'] = this.failed; - } else { - // json[r'failed'] = null; + if (this.failed.isPresent) { + final value = this.failed.value; + json[r'failed'] = value; } return json; } @@ -56,7 +55,7 @@ class QueueDeleteDto { final json = value.cast(); return QueueDeleteDto( - failed: mapValueOfType(json, r'failed'), + failed: json.containsKey(r'failed') ? Optional.present(mapValueOfType(json, r'failed')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/queue_job_response_dto.dart b/mobile/openapi/lib/model/queue_job_response_dto.dart index 06d433edad..ca26361a3e 100644 --- a/mobile/openapi/lib/model/queue_job_response_dto.dart +++ b/mobile/openapi/lib/model/queue_job_response_dto.dart @@ -14,7 +14,7 @@ class QueueJobResponseDto { /// Returns a new [QueueJobResponseDto] instance. QueueJobResponseDto({ this.data = const {}, - this.id, + this.id = const Optional.absent(), required this.name, required this.timestamp, }); @@ -29,7 +29,7 @@ class QueueJobResponseDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? id; + Optional id; JobName name; @@ -60,10 +60,9 @@ class QueueJobResponseDto { Map toJson() { final json = {}; json[r'data'] = this.data; - if (this.id != null) { - json[r'id'] = this.id; - } else { - // json[r'id'] = null; + if (this.id.isPresent) { + final value = this.id.value; + json[r'id'] = value; } json[r'name'] = this.name; json[r'timestamp'] = this.timestamp; @@ -80,7 +79,7 @@ class QueueJobResponseDto { return QueueJobResponseDto( data: mapCastOfType(json, r'data')!, - id: mapValueOfType(json, r'id'), + id: json.containsKey(r'id') ? Optional.present(mapValueOfType(json, r'id')) : const Optional.absent(), name: JobName.fromJson(json[r'name'])!, timestamp: mapValueOfType(json, r'timestamp')!, ); diff --git a/mobile/openapi/lib/model/queue_update_dto.dart b/mobile/openapi/lib/model/queue_update_dto.dart index 28aafe95f7..189fc219f1 100644 --- a/mobile/openapi/lib/model/queue_update_dto.dart +++ b/mobile/openapi/lib/model/queue_update_dto.dart @@ -13,7 +13,7 @@ part of openapi.api; class QueueUpdateDto { /// Returns a new [QueueUpdateDto] instance. QueueUpdateDto({ - this.isPaused, + this.isPaused = const Optional.absent(), }); /// Whether to pause the queue @@ -23,7 +23,7 @@ class QueueUpdateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? isPaused; + Optional isPaused; @override bool operator ==(Object other) => identical(this, other) || other is QueueUpdateDto && @@ -39,10 +39,9 @@ class QueueUpdateDto { Map toJson() { final json = {}; - if (this.isPaused != null) { - json[r'isPaused'] = this.isPaused; - } else { - // json[r'isPaused'] = null; + if (this.isPaused.isPresent) { + final value = this.isPaused.value; + json[r'isPaused'] = value; } return json; } @@ -56,7 +55,7 @@ class QueueUpdateDto { final json = value.cast(); return QueueUpdateDto( - isPaused: mapValueOfType(json, r'isPaused'), + isPaused: json.containsKey(r'isPaused') ? Optional.present(mapValueOfType(json, r'isPaused')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/random_search_dto.dart b/mobile/openapi/lib/model/random_search_dto.dart index 728072639c..89d0fd4d35 100644 --- a/mobile/openapi/lib/model/random_search_dto.dart +++ b/mobile/openapi/lib/model/random_search_dto.dart @@ -13,48 +13,48 @@ part of openapi.api; class RandomSearchDto { /// Returns a new [RandomSearchDto] instance. RandomSearchDto({ - this.albumIds = const [], - this.city, - this.country, - this.createdAfter, - this.createdBefore, - this.isEncoded, - this.isFavorite, - this.isMotion, - this.isNotInAlbum, - this.isOffline, - this.lensModel, - this.libraryId, - this.make, - this.model, - this.ocr, - this.personIds = const [], - this.rating, - this.size, - this.state, - this.tagIds = const [], - this.takenAfter, - this.takenBefore, - this.trashedAfter, - this.trashedBefore, - this.type, - this.updatedAfter, - this.updatedBefore, - this.visibility, - this.withDeleted, - this.withExif, - this.withPeople, - this.withStacked, + this.albumIds = const Optional.present(const []), + this.city = const Optional.absent(), + this.country = const Optional.absent(), + this.createdAfter = const Optional.absent(), + this.createdBefore = const Optional.absent(), + this.isEncoded = const Optional.absent(), + this.isFavorite = const Optional.absent(), + this.isMotion = const Optional.absent(), + this.isNotInAlbum = const Optional.absent(), + this.isOffline = const Optional.absent(), + this.lensModel = const Optional.absent(), + this.libraryId = const Optional.absent(), + this.make = const Optional.absent(), + this.model = const Optional.absent(), + this.ocr = const Optional.absent(), + this.personIds = const Optional.present(const []), + this.rating = const Optional.absent(), + this.size = const Optional.absent(), + this.state = const Optional.absent(), + this.tagIds = const Optional.present(const []), + this.takenAfter = const Optional.absent(), + this.takenBefore = const Optional.absent(), + this.trashedAfter = const Optional.absent(), + this.trashedBefore = const Optional.absent(), + this.type = const Optional.absent(), + this.updatedAfter = const Optional.absent(), + this.updatedBefore = const Optional.absent(), + this.visibility = const Optional.absent(), + this.withDeleted = const Optional.absent(), + this.withExif = const Optional.absent(), + this.withPeople = const Optional.absent(), + this.withStacked = const Optional.absent(), }); /// Filter by album IDs - List albumIds; + Optional?> albumIds; /// Filter by city name - String? city; + Optional city; /// Filter by country name - String? country; + Optional country; /// Filter by creation date (after) /// @@ -63,7 +63,7 @@ class RandomSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - DateTime? createdAfter; + Optional createdAfter; /// Filter by creation date (before) /// @@ -72,7 +72,7 @@ class RandomSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - DateTime? createdBefore; + Optional createdBefore; /// Filter by encoded status /// @@ -81,7 +81,7 @@ class RandomSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? isEncoded; + Optional isEncoded; /// Filter by favorite status /// @@ -90,7 +90,7 @@ class RandomSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? isFavorite; + Optional isFavorite; /// Filter by motion photo status /// @@ -99,7 +99,7 @@ class RandomSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? isMotion; + Optional isMotion; /// Filter assets not in any album /// @@ -108,7 +108,7 @@ class RandomSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? isNotInAlbum; + Optional isNotInAlbum; /// Filter by offline status /// @@ -117,19 +117,19 @@ class RandomSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? isOffline; + Optional isOffline; /// Filter by lens model - String? lensModel; + Optional lensModel; /// Library ID to filter by - String? libraryId; + Optional libraryId; /// Filter by camera make - String? make; + Optional make; /// Filter by camera model - String? model; + Optional model; /// Filter by OCR text content /// @@ -138,16 +138,16 @@ class RandomSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? ocr; + Optional ocr; /// Filter by person IDs - List personIds; + Optional?> personIds; /// Filter by rating [1-5], or null for unrated /// /// Minimum value: -1 /// Maximum value: 5 - int? rating; + Optional rating; /// Number of results to return /// @@ -159,13 +159,13 @@ class RandomSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - int? size; + Optional size; /// Filter by state/province name - String? state; + Optional state; /// Filter by tag IDs - List? tagIds; + Optional?> tagIds; /// Filter by taken date (after) /// @@ -174,7 +174,7 @@ class RandomSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - DateTime? takenAfter; + Optional takenAfter; /// Filter by taken date (before) /// @@ -183,7 +183,7 @@ class RandomSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - DateTime? takenBefore; + Optional takenBefore; /// Filter by trash date (after) /// @@ -192,7 +192,7 @@ class RandomSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - DateTime? trashedAfter; + Optional trashedAfter; /// Filter by trash date (before) /// @@ -201,7 +201,7 @@ class RandomSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - DateTime? trashedBefore; + Optional trashedBefore; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -209,7 +209,7 @@ class RandomSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - AssetTypeEnum? type; + Optional type; /// Filter by update date (after) /// @@ -218,7 +218,7 @@ class RandomSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - DateTime? updatedAfter; + Optional updatedAfter; /// Filter by update date (before) /// @@ -227,7 +227,7 @@ class RandomSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - DateTime? updatedBefore; + Optional updatedBefore; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -235,7 +235,7 @@ class RandomSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - AssetVisibility? visibility; + Optional visibility; /// Include deleted assets /// @@ -244,7 +244,7 @@ class RandomSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? withDeleted; + Optional withDeleted; /// Include EXIF data in response /// @@ -253,7 +253,7 @@ class RandomSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? withExif; + Optional withExif; /// Include people data in response /// @@ -262,7 +262,7 @@ class RandomSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? withPeople; + Optional withPeople; /// Include stacked assets /// @@ -271,7 +271,7 @@ class RandomSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? withStacked; + Optional withStacked; @override bool operator ==(Object other) => identical(this, other) || other is RandomSearchDto && @@ -349,173 +349,149 @@ class RandomSearchDto { Map toJson() { final json = {}; - json[r'albumIds'] = this.albumIds; - if (this.city != null) { - json[r'city'] = this.city; - } else { - // json[r'city'] = null; + if (this.albumIds.isPresent) { + final value = this.albumIds.value; + json[r'albumIds'] = value; } - if (this.country != null) { - json[r'country'] = this.country; - } else { - // json[r'country'] = null; + if (this.city.isPresent) { + final value = this.city.value; + json[r'city'] = value; } - if (this.createdAfter != null) { - json[r'createdAfter'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.createdAfter!.millisecondsSinceEpoch - : this.createdAfter!.toUtc().toIso8601String(); - } else { - // json[r'createdAfter'] = null; + if (this.country.isPresent) { + final value = this.country.value; + json[r'country'] = value; } - if (this.createdBefore != null) { - json[r'createdBefore'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.createdBefore!.millisecondsSinceEpoch - : this.createdBefore!.toUtc().toIso8601String(); - } else { - // json[r'createdBefore'] = null; + if (this.createdAfter.isPresent) { + final value = this.createdAfter.value; + json[r'createdAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.isEncoded != null) { - json[r'isEncoded'] = this.isEncoded; - } else { - // json[r'isEncoded'] = null; + if (this.createdBefore.isPresent) { + final value = this.createdBefore.value; + json[r'createdBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.isFavorite != null) { - json[r'isFavorite'] = this.isFavorite; - } else { - // json[r'isFavorite'] = null; + if (this.isEncoded.isPresent) { + final value = this.isEncoded.value; + json[r'isEncoded'] = value; } - if (this.isMotion != null) { - json[r'isMotion'] = this.isMotion; - } else { - // json[r'isMotion'] = null; + if (this.isFavorite.isPresent) { + final value = this.isFavorite.value; + json[r'isFavorite'] = value; } - if (this.isNotInAlbum != null) { - json[r'isNotInAlbum'] = this.isNotInAlbum; - } else { - // json[r'isNotInAlbum'] = null; + if (this.isMotion.isPresent) { + final value = this.isMotion.value; + json[r'isMotion'] = value; } - if (this.isOffline != null) { - json[r'isOffline'] = this.isOffline; - } else { - // json[r'isOffline'] = null; + if (this.isNotInAlbum.isPresent) { + final value = this.isNotInAlbum.value; + json[r'isNotInAlbum'] = value; } - if (this.lensModel != null) { - json[r'lensModel'] = this.lensModel; - } else { - // json[r'lensModel'] = null; + if (this.isOffline.isPresent) { + final value = this.isOffline.value; + json[r'isOffline'] = value; } - if (this.libraryId != null) { - json[r'libraryId'] = this.libraryId; - } else { - // json[r'libraryId'] = null; + if (this.lensModel.isPresent) { + final value = this.lensModel.value; + json[r'lensModel'] = value; } - if (this.make != null) { - json[r'make'] = this.make; - } else { - // json[r'make'] = null; + if (this.libraryId.isPresent) { + final value = this.libraryId.value; + json[r'libraryId'] = value; } - if (this.model != null) { - json[r'model'] = this.model; - } else { - // json[r'model'] = null; + if (this.make.isPresent) { + final value = this.make.value; + json[r'make'] = value; } - if (this.ocr != null) { - json[r'ocr'] = this.ocr; - } else { - // json[r'ocr'] = null; + if (this.model.isPresent) { + final value = this.model.value; + json[r'model'] = value; } - json[r'personIds'] = this.personIds; - if (this.rating != null) { - json[r'rating'] = this.rating; - } else { - // json[r'rating'] = null; + if (this.ocr.isPresent) { + final value = this.ocr.value; + json[r'ocr'] = value; } - if (this.size != null) { - json[r'size'] = this.size; - } else { - // json[r'size'] = null; + if (this.personIds.isPresent) { + final value = this.personIds.value; + json[r'personIds'] = value; } - if (this.state != null) { - json[r'state'] = this.state; - } else { - // json[r'state'] = null; + if (this.rating.isPresent) { + final value = this.rating.value; + json[r'rating'] = value; } - if (this.tagIds != null) { - json[r'tagIds'] = this.tagIds; - } else { - // json[r'tagIds'] = null; + if (this.size.isPresent) { + final value = this.size.value; + json[r'size'] = value; } - if (this.takenAfter != null) { - json[r'takenAfter'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.takenAfter!.millisecondsSinceEpoch - : this.takenAfter!.toUtc().toIso8601String(); - } else { - // json[r'takenAfter'] = null; + if (this.state.isPresent) { + final value = this.state.value; + json[r'state'] = value; } - if (this.takenBefore != null) { - json[r'takenBefore'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.takenBefore!.millisecondsSinceEpoch - : this.takenBefore!.toUtc().toIso8601String(); - } else { - // json[r'takenBefore'] = null; + if (this.tagIds.isPresent) { + final value = this.tagIds.value; + json[r'tagIds'] = value; } - if (this.trashedAfter != null) { - json[r'trashedAfter'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.trashedAfter!.millisecondsSinceEpoch - : this.trashedAfter!.toUtc().toIso8601String(); - } else { - // json[r'trashedAfter'] = null; + if (this.takenAfter.isPresent) { + final value = this.takenAfter.value; + json[r'takenAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.trashedBefore != null) { - json[r'trashedBefore'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.trashedBefore!.millisecondsSinceEpoch - : this.trashedBefore!.toUtc().toIso8601String(); - } else { - // json[r'trashedBefore'] = null; + if (this.takenBefore.isPresent) { + final value = this.takenBefore.value; + json[r'takenBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.type != null) { - json[r'type'] = this.type; - } else { - // json[r'type'] = null; + if (this.trashedAfter.isPresent) { + final value = this.trashedAfter.value; + json[r'trashedAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.updatedAfter != null) { - json[r'updatedAfter'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.updatedAfter!.millisecondsSinceEpoch - : this.updatedAfter!.toUtc().toIso8601String(); - } else { - // json[r'updatedAfter'] = null; + if (this.trashedBefore.isPresent) { + final value = this.trashedBefore.value; + json[r'trashedBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.updatedBefore != null) { - json[r'updatedBefore'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.updatedBefore!.millisecondsSinceEpoch - : this.updatedBefore!.toUtc().toIso8601String(); - } else { - // json[r'updatedBefore'] = null; + if (this.type.isPresent) { + final value = this.type.value; + json[r'type'] = value; } - if (this.visibility != null) { - json[r'visibility'] = this.visibility; - } else { - // json[r'visibility'] = null; + if (this.updatedAfter.isPresent) { + final value = this.updatedAfter.value; + json[r'updatedAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.withDeleted != null) { - json[r'withDeleted'] = this.withDeleted; - } else { - // json[r'withDeleted'] = null; + if (this.updatedBefore.isPresent) { + final value = this.updatedBefore.value; + json[r'updatedBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.withExif != null) { - json[r'withExif'] = this.withExif; - } else { - // json[r'withExif'] = null; + if (this.visibility.isPresent) { + final value = this.visibility.value; + json[r'visibility'] = value; } - if (this.withPeople != null) { - json[r'withPeople'] = this.withPeople; - } else { - // json[r'withPeople'] = null; + if (this.withDeleted.isPresent) { + final value = this.withDeleted.value; + json[r'withDeleted'] = value; } - if (this.withStacked != null) { - json[r'withStacked'] = this.withStacked; - } else { - // json[r'withStacked'] = null; + if (this.withExif.isPresent) { + final value = this.withExif.value; + json[r'withExif'] = value; + } + if (this.withPeople.isPresent) { + final value = this.withPeople.value; + json[r'withPeople'] = value; + } + if (this.withStacked.isPresent) { + final value = this.withStacked.value; + json[r'withStacked'] = value; } return json; } @@ -529,44 +505,44 @@ class RandomSearchDto { final json = value.cast(); return RandomSearchDto( - albumIds: json[r'albumIds'] is Iterable + albumIds: json.containsKey(r'albumIds') ? Optional.present(json[r'albumIds'] is Iterable ? (json[r'albumIds'] as Iterable).cast().toList(growable: false) - : const [], - city: mapValueOfType(json, r'city'), - country: mapValueOfType(json, r'country'), - createdAfter: mapDateTime(json, r'createdAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - createdBefore: mapDateTime(json, r'createdBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - isEncoded: mapValueOfType(json, r'isEncoded'), - isFavorite: mapValueOfType(json, r'isFavorite'), - isMotion: mapValueOfType(json, r'isMotion'), - isNotInAlbum: mapValueOfType(json, r'isNotInAlbum'), - isOffline: mapValueOfType(json, r'isOffline'), - lensModel: mapValueOfType(json, r'lensModel'), - libraryId: mapValueOfType(json, r'libraryId'), - make: mapValueOfType(json, r'make'), - model: mapValueOfType(json, r'model'), - ocr: mapValueOfType(json, r'ocr'), - personIds: json[r'personIds'] is Iterable + : const []) : const Optional.absent(), + city: json.containsKey(r'city') ? Optional.present(mapValueOfType(json, r'city')) : const Optional.absent(), + country: json.containsKey(r'country') ? Optional.present(mapValueOfType(json, r'country')) : const Optional.absent(), + createdAfter: json.containsKey(r'createdAfter') ? Optional.present(mapDateTime(json, r'createdAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + createdBefore: json.containsKey(r'createdBefore') ? Optional.present(mapDateTime(json, r'createdBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + isEncoded: json.containsKey(r'isEncoded') ? Optional.present(mapValueOfType(json, r'isEncoded')) : const Optional.absent(), + isFavorite: json.containsKey(r'isFavorite') ? Optional.present(mapValueOfType(json, r'isFavorite')) : const Optional.absent(), + isMotion: json.containsKey(r'isMotion') ? Optional.present(mapValueOfType(json, r'isMotion')) : const Optional.absent(), + isNotInAlbum: json.containsKey(r'isNotInAlbum') ? Optional.present(mapValueOfType(json, r'isNotInAlbum')) : const Optional.absent(), + isOffline: json.containsKey(r'isOffline') ? Optional.present(mapValueOfType(json, r'isOffline')) : const Optional.absent(), + lensModel: json.containsKey(r'lensModel') ? Optional.present(mapValueOfType(json, r'lensModel')) : const Optional.absent(), + libraryId: json.containsKey(r'libraryId') ? Optional.present(mapValueOfType(json, r'libraryId')) : const Optional.absent(), + make: json.containsKey(r'make') ? Optional.present(mapValueOfType(json, r'make')) : const Optional.absent(), + model: json.containsKey(r'model') ? Optional.present(mapValueOfType(json, r'model')) : const Optional.absent(), + ocr: json.containsKey(r'ocr') ? Optional.present(mapValueOfType(json, r'ocr')) : const Optional.absent(), + personIds: json.containsKey(r'personIds') ? Optional.present(json[r'personIds'] is Iterable ? (json[r'personIds'] as Iterable).cast().toList(growable: false) - : const [], - rating: mapValueOfType(json, r'rating'), - size: mapValueOfType(json, r'size'), - state: mapValueOfType(json, r'state'), - tagIds: json[r'tagIds'] is Iterable + : const []) : const Optional.absent(), + rating: json.containsKey(r'rating') ? Optional.present(json[r'rating'] == null ? null : int.parse('${json[r'rating']}')) : const Optional.absent(), + size: json.containsKey(r'size') ? Optional.present(json[r'size'] == null ? null : int.parse('${json[r'size']}')) : const Optional.absent(), + state: json.containsKey(r'state') ? Optional.present(mapValueOfType(json, r'state')) : const Optional.absent(), + tagIds: json.containsKey(r'tagIds') ? Optional.present(json[r'tagIds'] is Iterable ? (json[r'tagIds'] as Iterable).cast().toList(growable: false) - : const [], - takenAfter: mapDateTime(json, r'takenAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - takenBefore: mapDateTime(json, r'takenBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - trashedAfter: mapDateTime(json, r'trashedAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - trashedBefore: mapDateTime(json, r'trashedBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - type: AssetTypeEnum.fromJson(json[r'type']), - updatedAfter: mapDateTime(json, r'updatedAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - updatedBefore: mapDateTime(json, r'updatedBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - visibility: AssetVisibility.fromJson(json[r'visibility']), - withDeleted: mapValueOfType(json, r'withDeleted'), - withExif: mapValueOfType(json, r'withExif'), - withPeople: mapValueOfType(json, r'withPeople'), - withStacked: mapValueOfType(json, r'withStacked'), + : const []) : const Optional.absent(), + takenAfter: json.containsKey(r'takenAfter') ? Optional.present(mapDateTime(json, r'takenAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + takenBefore: json.containsKey(r'takenBefore') ? Optional.present(mapDateTime(json, r'takenBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + trashedAfter: json.containsKey(r'trashedAfter') ? Optional.present(mapDateTime(json, r'trashedAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + trashedBefore: json.containsKey(r'trashedBefore') ? Optional.present(mapDateTime(json, r'trashedBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + type: json.containsKey(r'type') ? Optional.present(AssetTypeEnum.fromJson(json[r'type'])) : const Optional.absent(), + updatedAfter: json.containsKey(r'updatedAfter') ? Optional.present(mapDateTime(json, r'updatedAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + updatedBefore: json.containsKey(r'updatedBefore') ? Optional.present(mapDateTime(json, r'updatedBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + visibility: json.containsKey(r'visibility') ? Optional.present(AssetVisibility.fromJson(json[r'visibility'])) : const Optional.absent(), + withDeleted: json.containsKey(r'withDeleted') ? Optional.present(mapValueOfType(json, r'withDeleted')) : const Optional.absent(), + withExif: json.containsKey(r'withExif') ? Optional.present(mapValueOfType(json, r'withExif')) : const Optional.absent(), + withPeople: json.containsKey(r'withPeople') ? Optional.present(mapValueOfType(json, r'withPeople')) : const Optional.absent(), + withStacked: json.containsKey(r'withStacked') ? Optional.present(mapValueOfType(json, r'withStacked')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/ratings_update.dart b/mobile/openapi/lib/model/ratings_update.dart index 8079172e21..085efd97e6 100644 --- a/mobile/openapi/lib/model/ratings_update.dart +++ b/mobile/openapi/lib/model/ratings_update.dart @@ -13,7 +13,7 @@ part of openapi.api; class RatingsUpdate { /// Returns a new [RatingsUpdate] instance. RatingsUpdate({ - this.enabled, + this.enabled = const Optional.absent(), }); /// Whether ratings are enabled @@ -23,7 +23,7 @@ class RatingsUpdate { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? enabled; + Optional enabled; @override bool operator ==(Object other) => identical(this, other) || other is RatingsUpdate && @@ -39,10 +39,9 @@ class RatingsUpdate { Map toJson() { final json = {}; - if (this.enabled != null) { - json[r'enabled'] = this.enabled; - } else { - // json[r'enabled'] = null; + if (this.enabled.isPresent) { + final value = this.enabled.value; + json[r'enabled'] = value; } return json; } @@ -56,7 +55,7 @@ class RatingsUpdate { final json = value.cast(); return RatingsUpdate( - enabled: mapValueOfType(json, r'enabled'), + enabled: json.containsKey(r'enabled') ? Optional.present(mapValueOfType(json, r'enabled')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/release_channel.dart b/mobile/openapi/lib/model/release_channel.dart new file mode 100644 index 0000000000..48b082af07 --- /dev/null +++ b/mobile/openapi/lib/model/release_channel.dart @@ -0,0 +1,85 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +/// Release channel +class ReleaseChannel { + /// Instantiate a new enum with the provided [value]. + const ReleaseChannel._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const stable = ReleaseChannel._(r'stable'); + static const releaseCandidate = ReleaseChannel._(r'releaseCandidate'); + + /// List of all possible values in this [enum][ReleaseChannel]. + static const values = [ + stable, + releaseCandidate, + ]; + + static ReleaseChannel? fromJson(dynamic value) => ReleaseChannelTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = ReleaseChannel.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [ReleaseChannel] to String, +/// and [decode] dynamic data back to [ReleaseChannel]. +class ReleaseChannelTypeTransformer { + factory ReleaseChannelTypeTransformer() => _instance ??= const ReleaseChannelTypeTransformer._(); + + const ReleaseChannelTypeTransformer._(); + + String encode(ReleaseChannel data) => data.value; + + /// Decodes a [dynamic value][data] to a ReleaseChannel. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + ReleaseChannel? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'stable': return ReleaseChannel.stable; + case r'releaseCandidate': return ReleaseChannel.releaseCandidate; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [ReleaseChannelTypeTransformer] instance. + static ReleaseChannelTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/release_event_v1.dart b/mobile/openapi/lib/model/release_event_v1.dart new file mode 100644 index 0000000000..f26ae3e96e --- /dev/null +++ b/mobile/openapi/lib/model/release_event_v1.dart @@ -0,0 +1,133 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class ReleaseEventV1 { + /// Returns a new [ReleaseEventV1] instance. + ReleaseEventV1({ + required this.checkedAt, + required this.isAvailable, + required this.releaseVersion, + required this.serverVersion, + required this.type, + }); + + /// When the server last checked for a latest version. As an ISO timestamp + String checkedAt; + + /// Whether a new version is available + bool isAvailable; + + ServerVersionResponseDto releaseVersion; + + ServerVersionResponseDto serverVersion; + + ReleaseType type; + + @override + bool operator ==(Object other) => identical(this, other) || other is ReleaseEventV1 && + other.checkedAt == checkedAt && + other.isAvailable == isAvailable && + other.releaseVersion == releaseVersion && + other.serverVersion == serverVersion && + other.type == type; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (checkedAt.hashCode) + + (isAvailable.hashCode) + + (releaseVersion.hashCode) + + (serverVersion.hashCode) + + (type.hashCode); + + @override + String toString() => 'ReleaseEventV1[checkedAt=$checkedAt, isAvailable=$isAvailable, releaseVersion=$releaseVersion, serverVersion=$serverVersion, type=$type]'; + + Map toJson() { + final json = {}; + json[r'checkedAt'] = this.checkedAt; + json[r'isAvailable'] = this.isAvailable; + json[r'releaseVersion'] = this.releaseVersion; + json[r'serverVersion'] = this.serverVersion; + json[r'type'] = this.type; + return json; + } + + /// Returns a new [ReleaseEventV1] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static ReleaseEventV1? fromJson(dynamic value) { + upgradeDto(value, "ReleaseEventV1"); + if (value is Map) { + final json = value.cast(); + + return ReleaseEventV1( + checkedAt: mapValueOfType(json, r'checkedAt')!, + isAvailable: mapValueOfType(json, r'isAvailable')!, + releaseVersion: ServerVersionResponseDto.fromJson(json[r'releaseVersion'])!, + serverVersion: ServerVersionResponseDto.fromJson(json[r'serverVersion'])!, + type: ReleaseType.fromJson(json[r'type'])!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = ReleaseEventV1.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = ReleaseEventV1.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of ReleaseEventV1-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = ReleaseEventV1.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'checkedAt', + 'isAvailable', + 'releaseVersion', + 'serverVersion', + 'type', + }; +} + diff --git a/mobile/openapi/lib/model/release_type.dart b/mobile/openapi/lib/model/release_type.dart new file mode 100644 index 0000000000..2d61072286 --- /dev/null +++ b/mobile/openapi/lib/model/release_type.dart @@ -0,0 +1,100 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + + +class ReleaseType { + /// Instantiate a new enum with the provided [value]. + const ReleaseType._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const major = ReleaseType._(r'major'); + static const premajor = ReleaseType._(r'premajor'); + static const minor = ReleaseType._(r'minor'); + static const preminor = ReleaseType._(r'preminor'); + static const patch_ = ReleaseType._(r'patch'); + static const prepatch = ReleaseType._(r'prepatch'); + static const prerelease = ReleaseType._(r'prerelease'); + + /// List of all possible values in this [enum][ReleaseType]. + static const values = [ + major, + premajor, + minor, + preminor, + patch_, + prepatch, + prerelease, + ]; + + static ReleaseType? fromJson(dynamic value) => ReleaseTypeTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = ReleaseType.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [ReleaseType] to String, +/// and [decode] dynamic data back to [ReleaseType]. +class ReleaseTypeTypeTransformer { + factory ReleaseTypeTypeTransformer() => _instance ??= const ReleaseTypeTypeTransformer._(); + + const ReleaseTypeTypeTransformer._(); + + String encode(ReleaseType data) => data.value; + + /// Decodes a [dynamic value][data] to a ReleaseType. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + ReleaseType? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'major': return ReleaseType.major; + case r'premajor': return ReleaseType.premajor; + case r'minor': return ReleaseType.minor; + case r'preminor': return ReleaseType.preminor; + case r'patch': return ReleaseType.patch_; + case r'prepatch': return ReleaseType.prepatch; + case r'prerelease': return ReleaseType.prerelease; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [ReleaseTypeTypeTransformer] instance. + static ReleaseTypeTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/reverse_geocoding_state_response_dto.dart b/mobile/openapi/lib/model/reverse_geocoding_state_response_dto.dart index 6ad8c1a7b9..07bb226ac0 100644 --- a/mobile/openapi/lib/model/reverse_geocoding_state_response_dto.dart +++ b/mobile/openapi/lib/model/reverse_geocoding_state_response_dto.dart @@ -42,12 +42,12 @@ class ReverseGeocodingStateResponseDto { if (this.lastImportFileName != null) { json[r'lastImportFileName'] = this.lastImportFileName; } else { - // json[r'lastImportFileName'] = null; + json[r'lastImportFileName'] = null; } if (this.lastUpdate != null) { json[r'lastUpdate'] = this.lastUpdate; } else { - // json[r'lastUpdate'] = null; + json[r'lastUpdate'] = null; } return json; } diff --git a/mobile/openapi/lib/model/search_asset_response_dto.dart b/mobile/openapi/lib/model/search_asset_response_dto.dart index f4ffade26b..82971c3c49 100644 --- a/mobile/openapi/lib/model/search_asset_response_dto.dart +++ b/mobile/openapi/lib/model/search_asset_response_dto.dart @@ -67,7 +67,7 @@ class SearchAssetResponseDto { if (this.nextPage != null) { json[r'nextPage'] = this.nextPage; } else { - // json[r'nextPage'] = null; + json[r'nextPage'] = null; } json[r'total'] = this.total; return json; diff --git a/mobile/openapi/lib/model/server_about_response_dto.dart b/mobile/openapi/lib/model/server_about_response_dto.dart index 1ae53763fe..dcfb279204 100644 --- a/mobile/openapi/lib/model/server_about_response_dto.dart +++ b/mobile/openapi/lib/model/server_about_response_dto.dart @@ -13,25 +13,25 @@ part of openapi.api; class ServerAboutResponseDto { /// Returns a new [ServerAboutResponseDto] instance. ServerAboutResponseDto({ - this.build, - this.buildImage, - this.buildImageUrl, - this.buildUrl, - this.exiftool, - this.ffmpeg, - this.imagemagick, - this.libvips, + this.build = const Optional.absent(), + this.buildImage = const Optional.absent(), + this.buildImageUrl = const Optional.absent(), + this.buildUrl = const Optional.absent(), + this.exiftool = const Optional.absent(), + this.ffmpeg = const Optional.absent(), + this.imagemagick = const Optional.absent(), + this.libvips = const Optional.absent(), required this.licensed, - this.nodejs, - this.repository, - this.repositoryUrl, - this.sourceCommit, - this.sourceRef, - this.sourceUrl, - this.thirdPartyBugFeatureUrl, - this.thirdPartyDocumentationUrl, - this.thirdPartySourceUrl, - this.thirdPartySupportUrl, + this.nodejs = const Optional.absent(), + this.repository = const Optional.absent(), + this.repositoryUrl = const Optional.absent(), + this.sourceCommit = const Optional.absent(), + this.sourceRef = const Optional.absent(), + this.sourceUrl = const Optional.absent(), + this.thirdPartyBugFeatureUrl = const Optional.absent(), + this.thirdPartyDocumentationUrl = const Optional.absent(), + this.thirdPartySourceUrl = const Optional.absent(), + this.thirdPartySupportUrl = const Optional.absent(), required this.version, required this.versionUrl, }); @@ -43,7 +43,7 @@ class ServerAboutResponseDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? build; + Optional build; /// Build image name /// @@ -52,7 +52,7 @@ class ServerAboutResponseDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? buildImage; + Optional buildImage; /// Build image URL /// @@ -61,7 +61,7 @@ class ServerAboutResponseDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? buildImageUrl; + Optional buildImageUrl; /// Build URL /// @@ -70,7 +70,7 @@ class ServerAboutResponseDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? buildUrl; + Optional buildUrl; /// ExifTool version /// @@ -79,7 +79,7 @@ class ServerAboutResponseDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? exiftool; + Optional exiftool; /// FFmpeg version /// @@ -88,7 +88,7 @@ class ServerAboutResponseDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? ffmpeg; + Optional ffmpeg; /// ImageMagick version /// @@ -97,7 +97,7 @@ class ServerAboutResponseDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? imagemagick; + Optional imagemagick; /// libvips version /// @@ -106,7 +106,7 @@ class ServerAboutResponseDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? libvips; + Optional libvips; /// Whether the server is licensed bool licensed; @@ -118,7 +118,7 @@ class ServerAboutResponseDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? nodejs; + Optional nodejs; /// Repository name /// @@ -127,7 +127,7 @@ class ServerAboutResponseDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? repository; + Optional repository; /// Repository URL /// @@ -136,7 +136,7 @@ class ServerAboutResponseDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? repositoryUrl; + Optional repositoryUrl; /// Source commit hash /// @@ -145,7 +145,7 @@ class ServerAboutResponseDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? sourceCommit; + Optional sourceCommit; /// Source reference (branch/tag) /// @@ -154,7 +154,7 @@ class ServerAboutResponseDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? sourceRef; + Optional sourceRef; /// Source URL /// @@ -163,7 +163,7 @@ class ServerAboutResponseDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? sourceUrl; + Optional sourceUrl; /// Third-party bug/feature URL /// @@ -172,7 +172,7 @@ class ServerAboutResponseDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? thirdPartyBugFeatureUrl; + Optional thirdPartyBugFeatureUrl; /// Third-party documentation URL /// @@ -181,7 +181,7 @@ class ServerAboutResponseDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? thirdPartyDocumentationUrl; + Optional thirdPartyDocumentationUrl; /// Third-party source URL /// @@ -190,7 +190,7 @@ class ServerAboutResponseDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? thirdPartySourceUrl; + Optional thirdPartySourceUrl; /// Third-party support URL /// @@ -199,7 +199,7 @@ class ServerAboutResponseDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? thirdPartySupportUrl; + Optional thirdPartySupportUrl; /// Server version String version; @@ -261,96 +261,78 @@ class ServerAboutResponseDto { Map toJson() { final json = {}; - if (this.build != null) { - json[r'build'] = this.build; - } else { - // json[r'build'] = null; + if (this.build.isPresent) { + final value = this.build.value; + json[r'build'] = value; } - if (this.buildImage != null) { - json[r'buildImage'] = this.buildImage; - } else { - // json[r'buildImage'] = null; + if (this.buildImage.isPresent) { + final value = this.buildImage.value; + json[r'buildImage'] = value; } - if (this.buildImageUrl != null) { - json[r'buildImageUrl'] = this.buildImageUrl; - } else { - // json[r'buildImageUrl'] = null; + if (this.buildImageUrl.isPresent) { + final value = this.buildImageUrl.value; + json[r'buildImageUrl'] = value; } - if (this.buildUrl != null) { - json[r'buildUrl'] = this.buildUrl; - } else { - // json[r'buildUrl'] = null; + if (this.buildUrl.isPresent) { + final value = this.buildUrl.value; + json[r'buildUrl'] = value; } - if (this.exiftool != null) { - json[r'exiftool'] = this.exiftool; - } else { - // json[r'exiftool'] = null; + if (this.exiftool.isPresent) { + final value = this.exiftool.value; + json[r'exiftool'] = value; } - if (this.ffmpeg != null) { - json[r'ffmpeg'] = this.ffmpeg; - } else { - // json[r'ffmpeg'] = null; + if (this.ffmpeg.isPresent) { + final value = this.ffmpeg.value; + json[r'ffmpeg'] = value; } - if (this.imagemagick != null) { - json[r'imagemagick'] = this.imagemagick; - } else { - // json[r'imagemagick'] = null; + if (this.imagemagick.isPresent) { + final value = this.imagemagick.value; + json[r'imagemagick'] = value; } - if (this.libvips != null) { - json[r'libvips'] = this.libvips; - } else { - // json[r'libvips'] = null; + if (this.libvips.isPresent) { + final value = this.libvips.value; + json[r'libvips'] = value; } json[r'licensed'] = this.licensed; - if (this.nodejs != null) { - json[r'nodejs'] = this.nodejs; - } else { - // json[r'nodejs'] = null; + if (this.nodejs.isPresent) { + final value = this.nodejs.value; + json[r'nodejs'] = value; } - if (this.repository != null) { - json[r'repository'] = this.repository; - } else { - // json[r'repository'] = null; + if (this.repository.isPresent) { + final value = this.repository.value; + json[r'repository'] = value; } - if (this.repositoryUrl != null) { - json[r'repositoryUrl'] = this.repositoryUrl; - } else { - // json[r'repositoryUrl'] = null; + if (this.repositoryUrl.isPresent) { + final value = this.repositoryUrl.value; + json[r'repositoryUrl'] = value; } - if (this.sourceCommit != null) { - json[r'sourceCommit'] = this.sourceCommit; - } else { - // json[r'sourceCommit'] = null; + if (this.sourceCommit.isPresent) { + final value = this.sourceCommit.value; + json[r'sourceCommit'] = value; } - if (this.sourceRef != null) { - json[r'sourceRef'] = this.sourceRef; - } else { - // json[r'sourceRef'] = null; + if (this.sourceRef.isPresent) { + final value = this.sourceRef.value; + json[r'sourceRef'] = value; } - if (this.sourceUrl != null) { - json[r'sourceUrl'] = this.sourceUrl; - } else { - // json[r'sourceUrl'] = null; + if (this.sourceUrl.isPresent) { + final value = this.sourceUrl.value; + json[r'sourceUrl'] = value; } - if (this.thirdPartyBugFeatureUrl != null) { - json[r'thirdPartyBugFeatureUrl'] = this.thirdPartyBugFeatureUrl; - } else { - // json[r'thirdPartyBugFeatureUrl'] = null; + if (this.thirdPartyBugFeatureUrl.isPresent) { + final value = this.thirdPartyBugFeatureUrl.value; + json[r'thirdPartyBugFeatureUrl'] = value; } - if (this.thirdPartyDocumentationUrl != null) { - json[r'thirdPartyDocumentationUrl'] = this.thirdPartyDocumentationUrl; - } else { - // json[r'thirdPartyDocumentationUrl'] = null; + if (this.thirdPartyDocumentationUrl.isPresent) { + final value = this.thirdPartyDocumentationUrl.value; + json[r'thirdPartyDocumentationUrl'] = value; } - if (this.thirdPartySourceUrl != null) { - json[r'thirdPartySourceUrl'] = this.thirdPartySourceUrl; - } else { - // json[r'thirdPartySourceUrl'] = null; + if (this.thirdPartySourceUrl.isPresent) { + final value = this.thirdPartySourceUrl.value; + json[r'thirdPartySourceUrl'] = value; } - if (this.thirdPartySupportUrl != null) { - json[r'thirdPartySupportUrl'] = this.thirdPartySupportUrl; - } else { - // json[r'thirdPartySupportUrl'] = null; + if (this.thirdPartySupportUrl.isPresent) { + final value = this.thirdPartySupportUrl.value; + json[r'thirdPartySupportUrl'] = value; } json[r'version'] = this.version; json[r'versionUrl'] = this.versionUrl; @@ -366,25 +348,25 @@ class ServerAboutResponseDto { final json = value.cast(); return ServerAboutResponseDto( - build: mapValueOfType(json, r'build'), - buildImage: mapValueOfType(json, r'buildImage'), - buildImageUrl: mapValueOfType(json, r'buildImageUrl'), - buildUrl: mapValueOfType(json, r'buildUrl'), - exiftool: mapValueOfType(json, r'exiftool'), - ffmpeg: mapValueOfType(json, r'ffmpeg'), - imagemagick: mapValueOfType(json, r'imagemagick'), - libvips: mapValueOfType(json, r'libvips'), + build: json.containsKey(r'build') ? Optional.present(mapValueOfType(json, r'build')) : const Optional.absent(), + buildImage: json.containsKey(r'buildImage') ? Optional.present(mapValueOfType(json, r'buildImage')) : const Optional.absent(), + buildImageUrl: json.containsKey(r'buildImageUrl') ? Optional.present(mapValueOfType(json, r'buildImageUrl')) : const Optional.absent(), + buildUrl: json.containsKey(r'buildUrl') ? Optional.present(mapValueOfType(json, r'buildUrl')) : const Optional.absent(), + exiftool: json.containsKey(r'exiftool') ? Optional.present(mapValueOfType(json, r'exiftool')) : const Optional.absent(), + ffmpeg: json.containsKey(r'ffmpeg') ? Optional.present(mapValueOfType(json, r'ffmpeg')) : const Optional.absent(), + imagemagick: json.containsKey(r'imagemagick') ? Optional.present(mapValueOfType(json, r'imagemagick')) : const Optional.absent(), + libvips: json.containsKey(r'libvips') ? Optional.present(mapValueOfType(json, r'libvips')) : const Optional.absent(), licensed: mapValueOfType(json, r'licensed')!, - nodejs: mapValueOfType(json, r'nodejs'), - repository: mapValueOfType(json, r'repository'), - repositoryUrl: mapValueOfType(json, r'repositoryUrl'), - sourceCommit: mapValueOfType(json, r'sourceCommit'), - sourceRef: mapValueOfType(json, r'sourceRef'), - sourceUrl: mapValueOfType(json, r'sourceUrl'), - thirdPartyBugFeatureUrl: mapValueOfType(json, r'thirdPartyBugFeatureUrl'), - thirdPartyDocumentationUrl: mapValueOfType(json, r'thirdPartyDocumentationUrl'), - thirdPartySourceUrl: mapValueOfType(json, r'thirdPartySourceUrl'), - thirdPartySupportUrl: mapValueOfType(json, r'thirdPartySupportUrl'), + nodejs: json.containsKey(r'nodejs') ? Optional.present(mapValueOfType(json, r'nodejs')) : const Optional.absent(), + repository: json.containsKey(r'repository') ? Optional.present(mapValueOfType(json, r'repository')) : const Optional.absent(), + repositoryUrl: json.containsKey(r'repositoryUrl') ? Optional.present(mapValueOfType(json, r'repositoryUrl')) : const Optional.absent(), + sourceCommit: json.containsKey(r'sourceCommit') ? Optional.present(mapValueOfType(json, r'sourceCommit')) : const Optional.absent(), + sourceRef: json.containsKey(r'sourceRef') ? Optional.present(mapValueOfType(json, r'sourceRef')) : const Optional.absent(), + sourceUrl: json.containsKey(r'sourceUrl') ? Optional.present(mapValueOfType(json, r'sourceUrl')) : const Optional.absent(), + thirdPartyBugFeatureUrl: json.containsKey(r'thirdPartyBugFeatureUrl') ? Optional.present(mapValueOfType(json, r'thirdPartyBugFeatureUrl')) : const Optional.absent(), + thirdPartyDocumentationUrl: json.containsKey(r'thirdPartyDocumentationUrl') ? Optional.present(mapValueOfType(json, r'thirdPartyDocumentationUrl')) : const Optional.absent(), + thirdPartySourceUrl: json.containsKey(r'thirdPartySourceUrl') ? Optional.present(mapValueOfType(json, r'thirdPartySourceUrl')) : const Optional.absent(), + thirdPartySupportUrl: json.containsKey(r'thirdPartySupportUrl') ? Optional.present(mapValueOfType(json, r'thirdPartySupportUrl')) : const Optional.absent(), version: mapValueOfType(json, r'version')!, versionUrl: mapValueOfType(json, r'versionUrl')!, ); diff --git a/mobile/openapi/lib/model/server_config_dto.dart b/mobile/openapi/lib/model/server_config_dto.dart index 316edb609f..0eaaec7c7f 100644 --- a/mobile/openapi/lib/model/server_config_dto.dart +++ b/mobile/openapi/lib/model/server_config_dto.dart @@ -20,6 +20,7 @@ class ServerConfigDto { required this.maintenanceMode, required this.mapDarkStyleUrl, required this.mapLightStyleUrl, + required this.minFaces, required this.oauthButtonText, required this.publicUsers, required this.trashDays, @@ -47,6 +48,12 @@ class ServerConfigDto { /// Map light style URL String mapLightStyleUrl; + /// People min faces server default + /// + /// Minimum value: -9007199254740991 + /// Maximum value: 9007199254740991 + int minFaces; + /// OAuth button text String oauthButtonText; @@ -74,6 +81,7 @@ class ServerConfigDto { other.maintenanceMode == maintenanceMode && other.mapDarkStyleUrl == mapDarkStyleUrl && other.mapLightStyleUrl == mapLightStyleUrl && + other.minFaces == minFaces && other.oauthButtonText == oauthButtonText && other.publicUsers == publicUsers && other.trashDays == trashDays && @@ -89,13 +97,14 @@ class ServerConfigDto { (maintenanceMode.hashCode) + (mapDarkStyleUrl.hashCode) + (mapLightStyleUrl.hashCode) + + (minFaces.hashCode) + (oauthButtonText.hashCode) + (publicUsers.hashCode) + (trashDays.hashCode) + (userDeleteDelay.hashCode); @override - String toString() => 'ServerConfigDto[externalDomain=$externalDomain, isInitialized=$isInitialized, isOnboarded=$isOnboarded, loginPageMessage=$loginPageMessage, maintenanceMode=$maintenanceMode, mapDarkStyleUrl=$mapDarkStyleUrl, mapLightStyleUrl=$mapLightStyleUrl, oauthButtonText=$oauthButtonText, publicUsers=$publicUsers, trashDays=$trashDays, userDeleteDelay=$userDeleteDelay]'; + String toString() => 'ServerConfigDto[externalDomain=$externalDomain, isInitialized=$isInitialized, isOnboarded=$isOnboarded, loginPageMessage=$loginPageMessage, maintenanceMode=$maintenanceMode, mapDarkStyleUrl=$mapDarkStyleUrl, mapLightStyleUrl=$mapLightStyleUrl, minFaces=$minFaces, oauthButtonText=$oauthButtonText, publicUsers=$publicUsers, trashDays=$trashDays, userDeleteDelay=$userDeleteDelay]'; Map toJson() { final json = {}; @@ -106,6 +115,7 @@ class ServerConfigDto { json[r'maintenanceMode'] = this.maintenanceMode; json[r'mapDarkStyleUrl'] = this.mapDarkStyleUrl; json[r'mapLightStyleUrl'] = this.mapLightStyleUrl; + json[r'minFaces'] = this.minFaces; json[r'oauthButtonText'] = this.oauthButtonText; json[r'publicUsers'] = this.publicUsers; json[r'trashDays'] = this.trashDays; @@ -129,6 +139,7 @@ class ServerConfigDto { maintenanceMode: mapValueOfType(json, r'maintenanceMode')!, mapDarkStyleUrl: mapValueOfType(json, r'mapDarkStyleUrl')!, mapLightStyleUrl: mapValueOfType(json, r'mapLightStyleUrl')!, + minFaces: mapValueOfType(json, r'minFaces')!, oauthButtonText: mapValueOfType(json, r'oauthButtonText')!, publicUsers: mapValueOfType(json, r'publicUsers')!, trashDays: mapValueOfType(json, r'trashDays')!, @@ -187,6 +198,7 @@ class ServerConfigDto { 'maintenanceMode', 'mapDarkStyleUrl', 'mapLightStyleUrl', + 'minFaces', 'oauthButtonText', 'publicUsers', 'trashDays', diff --git a/mobile/openapi/lib/model/server_features_dto.dart b/mobile/openapi/lib/model/server_features_dto.dart index 79494b74eb..9b75ef2b32 100644 --- a/mobile/openapi/lib/model/server_features_dto.dart +++ b/mobile/openapi/lib/model/server_features_dto.dart @@ -23,6 +23,7 @@ class ServerFeaturesDto { required this.oauthAutoLaunch, required this.ocr, required this.passwordLogin, + required this.realtimeTranscoding, required this.reverseGeocoding, required this.search, required this.sidecar, @@ -60,6 +61,9 @@ class ServerFeaturesDto { /// Whether password login is enabled bool passwordLogin; + /// Whether real-time transcoding is enabled + bool realtimeTranscoding; + /// Whether reverse geocoding is enabled bool reverseGeocoding; @@ -87,6 +91,7 @@ class ServerFeaturesDto { other.oauthAutoLaunch == oauthAutoLaunch && other.ocr == ocr && other.passwordLogin == passwordLogin && + other.realtimeTranscoding == realtimeTranscoding && other.reverseGeocoding == reverseGeocoding && other.search == search && other.sidecar == sidecar && @@ -106,6 +111,7 @@ class ServerFeaturesDto { (oauthAutoLaunch.hashCode) + (ocr.hashCode) + (passwordLogin.hashCode) + + (realtimeTranscoding.hashCode) + (reverseGeocoding.hashCode) + (search.hashCode) + (sidecar.hashCode) + @@ -113,7 +119,7 @@ class ServerFeaturesDto { (trash.hashCode); @override - String toString() => 'ServerFeaturesDto[configFile=$configFile, duplicateDetection=$duplicateDetection, email=$email, facialRecognition=$facialRecognition, importFaces=$importFaces, map=$map, oauth=$oauth, oauthAutoLaunch=$oauthAutoLaunch, ocr=$ocr, passwordLogin=$passwordLogin, reverseGeocoding=$reverseGeocoding, search=$search, sidecar=$sidecar, smartSearch=$smartSearch, trash=$trash]'; + String toString() => 'ServerFeaturesDto[configFile=$configFile, duplicateDetection=$duplicateDetection, email=$email, facialRecognition=$facialRecognition, importFaces=$importFaces, map=$map, oauth=$oauth, oauthAutoLaunch=$oauthAutoLaunch, ocr=$ocr, passwordLogin=$passwordLogin, realtimeTranscoding=$realtimeTranscoding, reverseGeocoding=$reverseGeocoding, search=$search, sidecar=$sidecar, smartSearch=$smartSearch, trash=$trash]'; Map toJson() { final json = {}; @@ -127,6 +133,7 @@ class ServerFeaturesDto { json[r'oauthAutoLaunch'] = this.oauthAutoLaunch; json[r'ocr'] = this.ocr; json[r'passwordLogin'] = this.passwordLogin; + json[r'realtimeTranscoding'] = this.realtimeTranscoding; json[r'reverseGeocoding'] = this.reverseGeocoding; json[r'search'] = this.search; json[r'sidecar'] = this.sidecar; @@ -154,6 +161,7 @@ class ServerFeaturesDto { oauthAutoLaunch: mapValueOfType(json, r'oauthAutoLaunch')!, ocr: mapValueOfType(json, r'ocr')!, passwordLogin: mapValueOfType(json, r'passwordLogin')!, + realtimeTranscoding: mapValueOfType(json, r'realtimeTranscoding')!, reverseGeocoding: mapValueOfType(json, r'reverseGeocoding')!, search: mapValueOfType(json, r'search')!, sidecar: mapValueOfType(json, r'sidecar')!, @@ -216,6 +224,7 @@ class ServerFeaturesDto { 'oauthAutoLaunch', 'ocr', 'passwordLogin', + 'realtimeTranscoding', 'reverseGeocoding', 'search', 'sidecar', diff --git a/mobile/openapi/lib/model/server_storage_response_dto.dart b/mobile/openapi/lib/model/server_storage_response_dto.dart index 4a66d54e37..f4f77c7f9b 100644 --- a/mobile/openapi/lib/model/server_storage_response_dto.dart +++ b/mobile/openapi/lib/model/server_storage_response_dto.dart @@ -101,7 +101,7 @@ class ServerStorageResponseDto { diskAvailableRaw: mapValueOfType(json, r'diskAvailableRaw')!, diskSize: mapValueOfType(json, r'diskSize')!, diskSizeRaw: mapValueOfType(json, r'diskSizeRaw')!, - diskUsagePercentage: (mapValueOfType(json, r'diskUsagePercentage')!).toDouble(), + diskUsagePercentage: mapValueOfType(json, r'diskUsagePercentage')!, diskUse: mapValueOfType(json, r'diskUse')!, diskUseRaw: mapValueOfType(json, r'diskUseRaw')!, ); diff --git a/mobile/openapi/lib/model/server_version_response_dto.dart b/mobile/openapi/lib/model/server_version_response_dto.dart index 60161a7458..8f4a192920 100644 --- a/mobile/openapi/lib/model/server_version_response_dto.dart +++ b/mobile/openapi/lib/model/server_version_response_dto.dart @@ -16,47 +16,61 @@ class ServerVersionResponseDto { required this.major, required this.minor, required this.patch_, + required this.prerelease, }); /// Major version number /// - /// Minimum value: -9007199254740991 + /// Minimum value: 0 /// Maximum value: 9007199254740991 int major; /// Minor version number /// - /// Minimum value: -9007199254740991 + /// Minimum value: 0 /// Maximum value: 9007199254740991 int minor; /// Patch version number /// - /// Minimum value: -9007199254740991 + /// Minimum value: 0 /// Maximum value: 9007199254740991 int patch_; + /// Pre-release version number + /// + /// Minimum value: 0 + /// Maximum value: 9007199254740991 + int? prerelease; + @override bool operator ==(Object other) => identical(this, other) || other is ServerVersionResponseDto && other.major == major && other.minor == minor && - other.patch_ == patch_; + other.patch_ == patch_ && + other.prerelease == prerelease; @override int get hashCode => // ignore: unnecessary_parenthesis (major.hashCode) + (minor.hashCode) + - (patch_.hashCode); + (patch_.hashCode) + + (prerelease == null ? 0 : prerelease!.hashCode); @override - String toString() => 'ServerVersionResponseDto[major=$major, minor=$minor, patch_=$patch_]'; + String toString() => 'ServerVersionResponseDto[major=$major, minor=$minor, patch_=$patch_, prerelease=$prerelease]'; Map toJson() { final json = {}; json[r'major'] = this.major; json[r'minor'] = this.minor; json[r'patch'] = this.patch_; + if (this.prerelease != null) { + json[r'prerelease'] = this.prerelease; + } else { + json[r'prerelease'] = null; + } return json; } @@ -72,6 +86,7 @@ class ServerVersionResponseDto { major: mapValueOfType(json, r'major')!, minor: mapValueOfType(json, r'minor')!, patch_: mapValueOfType(json, r'patch')!, + prerelease: mapValueOfType(json, r'prerelease'), ); } return null; @@ -122,6 +137,7 @@ class ServerVersionResponseDto { 'major', 'minor', 'patch', + 'prerelease', }; } diff --git a/mobile/openapi/lib/model/session_create_dto.dart b/mobile/openapi/lib/model/session_create_dto.dart index 37c07955cd..8033bb7f71 100644 --- a/mobile/openapi/lib/model/session_create_dto.dart +++ b/mobile/openapi/lib/model/session_create_dto.dart @@ -13,9 +13,9 @@ part of openapi.api; class SessionCreateDto { /// Returns a new [SessionCreateDto] instance. SessionCreateDto({ - this.deviceOS, - this.deviceType, - this.duration, + this.deviceOS = const Optional.absent(), + this.deviceType = const Optional.absent(), + this.duration = const Optional.absent(), }); /// Device OS @@ -25,7 +25,7 @@ class SessionCreateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? deviceOS; + Optional deviceOS; /// Device type /// @@ -34,7 +34,7 @@ class SessionCreateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? deviceType; + Optional deviceType; /// Session duration in seconds /// @@ -46,7 +46,7 @@ class SessionCreateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - int? duration; + Optional duration; @override bool operator ==(Object other) => identical(this, other) || other is SessionCreateDto && @@ -66,20 +66,17 @@ class SessionCreateDto { Map toJson() { final json = {}; - if (this.deviceOS != null) { - json[r'deviceOS'] = this.deviceOS; - } else { - // json[r'deviceOS'] = null; + if (this.deviceOS.isPresent) { + final value = this.deviceOS.value; + json[r'deviceOS'] = value; } - if (this.deviceType != null) { - json[r'deviceType'] = this.deviceType; - } else { - // json[r'deviceType'] = null; + if (this.deviceType.isPresent) { + final value = this.deviceType.value; + json[r'deviceType'] = value; } - if (this.duration != null) { - json[r'duration'] = this.duration; - } else { - // json[r'duration'] = null; + if (this.duration.isPresent) { + final value = this.duration.value; + json[r'duration'] = value; } return json; } @@ -93,9 +90,9 @@ class SessionCreateDto { final json = value.cast(); return SessionCreateDto( - deviceOS: mapValueOfType(json, r'deviceOS'), - deviceType: mapValueOfType(json, r'deviceType'), - duration: mapValueOfType(json, r'duration'), + deviceOS: json.containsKey(r'deviceOS') ? Optional.present(mapValueOfType(json, r'deviceOS')) : const Optional.absent(), + deviceType: json.containsKey(r'deviceType') ? Optional.present(mapValueOfType(json, r'deviceType')) : const Optional.absent(), + duration: json.containsKey(r'duration') ? Optional.present(json[r'duration'] == null ? null : int.parse('${json[r'duration']}')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/session_create_response_dto.dart b/mobile/openapi/lib/model/session_create_response_dto.dart index f35232b0e8..497da9afe8 100644 --- a/mobile/openapi/lib/model/session_create_response_dto.dart +++ b/mobile/openapi/lib/model/session_create_response_dto.dart @@ -18,7 +18,7 @@ class SessionCreateResponseDto { required this.current, required this.deviceOS, required this.deviceType, - this.expiresAt, + this.expiresAt = const Optional.absent(), required this.id, required this.isPendingSyncReset, required this.token, @@ -47,7 +47,7 @@ class SessionCreateResponseDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? expiresAt; + Optional expiresAt; /// Session ID String id; @@ -96,16 +96,15 @@ class SessionCreateResponseDto { if (this.appVersion != null) { json[r'appVersion'] = this.appVersion; } else { - // json[r'appVersion'] = null; + json[r'appVersion'] = null; } json[r'createdAt'] = this.createdAt; json[r'current'] = this.current; json[r'deviceOS'] = this.deviceOS; json[r'deviceType'] = this.deviceType; - if (this.expiresAt != null) { - json[r'expiresAt'] = this.expiresAt; - } else { - // json[r'expiresAt'] = null; + if (this.expiresAt.isPresent) { + final value = this.expiresAt.value; + json[r'expiresAt'] = value; } json[r'id'] = this.id; json[r'isPendingSyncReset'] = this.isPendingSyncReset; @@ -128,7 +127,7 @@ class SessionCreateResponseDto { current: mapValueOfType(json, r'current')!, deviceOS: mapValueOfType(json, r'deviceOS')!, deviceType: mapValueOfType(json, r'deviceType')!, - expiresAt: mapValueOfType(json, r'expiresAt'), + expiresAt: json.containsKey(r'expiresAt') ? Optional.present(mapValueOfType(json, r'expiresAt')) : const Optional.absent(), id: mapValueOfType(json, r'id')!, isPendingSyncReset: mapValueOfType(json, r'isPendingSyncReset')!, token: mapValueOfType(json, r'token')!, diff --git a/mobile/openapi/lib/model/session_response_dto.dart b/mobile/openapi/lib/model/session_response_dto.dart index ed84160827..e1e20619cb 100644 --- a/mobile/openapi/lib/model/session_response_dto.dart +++ b/mobile/openapi/lib/model/session_response_dto.dart @@ -18,7 +18,7 @@ class SessionResponseDto { required this.current, required this.deviceOS, required this.deviceType, - this.expiresAt, + this.expiresAt = const Optional.absent(), required this.id, required this.isPendingSyncReset, required this.updatedAt, @@ -46,7 +46,7 @@ class SessionResponseDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? expiresAt; + Optional expiresAt; /// Session ID String id; @@ -90,16 +90,15 @@ class SessionResponseDto { if (this.appVersion != null) { json[r'appVersion'] = this.appVersion; } else { - // json[r'appVersion'] = null; + json[r'appVersion'] = null; } json[r'createdAt'] = this.createdAt; json[r'current'] = this.current; json[r'deviceOS'] = this.deviceOS; json[r'deviceType'] = this.deviceType; - if (this.expiresAt != null) { - json[r'expiresAt'] = this.expiresAt; - } else { - // json[r'expiresAt'] = null; + if (this.expiresAt.isPresent) { + final value = this.expiresAt.value; + json[r'expiresAt'] = value; } json[r'id'] = this.id; json[r'isPendingSyncReset'] = this.isPendingSyncReset; @@ -121,7 +120,7 @@ class SessionResponseDto { current: mapValueOfType(json, r'current')!, deviceOS: mapValueOfType(json, r'deviceOS')!, deviceType: mapValueOfType(json, r'deviceType')!, - expiresAt: mapValueOfType(json, r'expiresAt'), + expiresAt: json.containsKey(r'expiresAt') ? Optional.present(mapValueOfType(json, r'expiresAt')) : const Optional.absent(), id: mapValueOfType(json, r'id')!, isPendingSyncReset: mapValueOfType(json, r'isPendingSyncReset')!, updatedAt: mapValueOfType(json, r'updatedAt')!, diff --git a/mobile/openapi/lib/model/session_unlock_dto.dart b/mobile/openapi/lib/model/session_unlock_dto.dart index 48ee75fb05..960b58acf0 100644 --- a/mobile/openapi/lib/model/session_unlock_dto.dart +++ b/mobile/openapi/lib/model/session_unlock_dto.dart @@ -13,8 +13,8 @@ part of openapi.api; class SessionUnlockDto { /// Returns a new [SessionUnlockDto] instance. SessionUnlockDto({ - this.password, - this.pinCode, + this.password = const Optional.absent(), + this.pinCode = const Optional.absent(), }); /// User password (required if PIN code is not provided) @@ -24,7 +24,7 @@ class SessionUnlockDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? password; + Optional password; /// New PIN code (4-6 digits) /// @@ -33,7 +33,7 @@ class SessionUnlockDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? pinCode; + Optional pinCode; @override bool operator ==(Object other) => identical(this, other) || other is SessionUnlockDto && @@ -51,15 +51,13 @@ class SessionUnlockDto { Map toJson() { final json = {}; - if (this.password != null) { - json[r'password'] = this.password; - } else { - // json[r'password'] = null; + if (this.password.isPresent) { + final value = this.password.value; + json[r'password'] = value; } - if (this.pinCode != null) { - json[r'pinCode'] = this.pinCode; - } else { - // json[r'pinCode'] = null; + if (this.pinCode.isPresent) { + final value = this.pinCode.value; + json[r'pinCode'] = value; } return json; } @@ -73,8 +71,8 @@ class SessionUnlockDto { final json = value.cast(); return SessionUnlockDto( - password: mapValueOfType(json, r'password'), - pinCode: mapValueOfType(json, r'pinCode'), + password: json.containsKey(r'password') ? Optional.present(mapValueOfType(json, r'password')) : const Optional.absent(), + pinCode: json.containsKey(r'pinCode') ? Optional.present(mapValueOfType(json, r'pinCode')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/session_update_dto.dart b/mobile/openapi/lib/model/session_update_dto.dart index 3ab430deaa..90cbaffaf4 100644 --- a/mobile/openapi/lib/model/session_update_dto.dart +++ b/mobile/openapi/lib/model/session_update_dto.dart @@ -13,7 +13,7 @@ part of openapi.api; class SessionUpdateDto { /// Returns a new [SessionUpdateDto] instance. SessionUpdateDto({ - this.isPendingSyncReset, + this.isPendingSyncReset = const Optional.absent(), }); /// Reset pending sync state @@ -23,7 +23,7 @@ class SessionUpdateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? isPendingSyncReset; + Optional isPendingSyncReset; @override bool operator ==(Object other) => identical(this, other) || other is SessionUpdateDto && @@ -39,10 +39,9 @@ class SessionUpdateDto { Map toJson() { final json = {}; - if (this.isPendingSyncReset != null) { - json[r'isPendingSyncReset'] = this.isPendingSyncReset; - } else { - // json[r'isPendingSyncReset'] = null; + if (this.isPendingSyncReset.isPresent) { + final value = this.isPendingSyncReset.value; + json[r'isPendingSyncReset'] = value; } return json; } @@ -56,7 +55,7 @@ class SessionUpdateDto { final json = value.cast(); return SessionUpdateDto( - isPendingSyncReset: mapValueOfType(json, r'isPendingSyncReset'), + isPendingSyncReset: json.containsKey(r'isPendingSyncReset') ? Optional.present(mapValueOfType(json, r'isPendingSyncReset')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/set_maintenance_mode_dto.dart b/mobile/openapi/lib/model/set_maintenance_mode_dto.dart index e7c9dc0d63..21f123bb84 100644 --- a/mobile/openapi/lib/model/set_maintenance_mode_dto.dart +++ b/mobile/openapi/lib/model/set_maintenance_mode_dto.dart @@ -14,7 +14,7 @@ class SetMaintenanceModeDto { /// Returns a new [SetMaintenanceModeDto] instance. SetMaintenanceModeDto({ required this.action, - this.restoreBackupFilename, + this.restoreBackupFilename = const Optional.absent(), }); MaintenanceAction action; @@ -26,7 +26,7 @@ class SetMaintenanceModeDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? restoreBackupFilename; + Optional restoreBackupFilename; @override bool operator ==(Object other) => identical(this, other) || other is SetMaintenanceModeDto && @@ -45,10 +45,9 @@ class SetMaintenanceModeDto { Map toJson() { final json = {}; json[r'action'] = this.action; - if (this.restoreBackupFilename != null) { - json[r'restoreBackupFilename'] = this.restoreBackupFilename; - } else { - // json[r'restoreBackupFilename'] = null; + if (this.restoreBackupFilename.isPresent) { + final value = this.restoreBackupFilename.value; + json[r'restoreBackupFilename'] = value; } return json; } @@ -63,7 +62,7 @@ class SetMaintenanceModeDto { return SetMaintenanceModeDto( action: MaintenanceAction.fromJson(json[r'action'])!, - restoreBackupFilename: mapValueOfType(json, r'restoreBackupFilename'), + restoreBackupFilename: json.containsKey(r'restoreBackupFilename') ? Optional.present(mapValueOfType(json, r'restoreBackupFilename')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/shared_link_create_dto.dart b/mobile/openapi/lib/model/shared_link_create_dto.dart index a32714d556..239db7f811 100644 --- a/mobile/openapi/lib/model/shared_link_create_dto.dart +++ b/mobile/openapi/lib/model/shared_link_create_dto.dart @@ -13,15 +13,15 @@ part of openapi.api; class SharedLinkCreateDto { /// Returns a new [SharedLinkCreateDto] instance. SharedLinkCreateDto({ - this.albumId, - this.allowDownload = true, - this.allowUpload, - this.assetIds = const [], - this.description, - this.expiresAt, - this.password, - this.showMetadata = true, - this.slug, + this.albumId = const Optional.absent(), + this.allowDownload = const Optional.present(true), + this.allowUpload = const Optional.absent(), + this.assetIds = const Optional.present(const []), + this.description = const Optional.absent(), + this.expiresAt = const Optional.absent(), + this.password = const Optional.absent(), + this.showMetadata = const Optional.present(true), + this.slug = const Optional.absent(), required this.type, }); @@ -32,10 +32,10 @@ class SharedLinkCreateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? albumId; + Optional albumId; /// Allow downloads - bool allowDownload; + Optional allowDownload; /// Allow uploads /// @@ -44,25 +44,25 @@ class SharedLinkCreateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? allowUpload; + Optional allowUpload; /// Asset IDs (for individual assets) - List assetIds; + Optional?> assetIds; /// Link description - String? description; + Optional description; /// Expiration date - DateTime? expiresAt; + Optional expiresAt; /// Link password - String? password; + Optional password; /// Show metadata - bool showMetadata; + Optional showMetadata; /// Custom URL slug - String? slug; + Optional slug; SharedLinkType type; @@ -98,40 +98,43 @@ class SharedLinkCreateDto { Map toJson() { final json = {}; - if (this.albumId != null) { - json[r'albumId'] = this.albumId; - } else { - // json[r'albumId'] = null; + if (this.albumId.isPresent) { + final value = this.albumId.value; + json[r'albumId'] = value; } - json[r'allowDownload'] = this.allowDownload; - if (this.allowUpload != null) { - json[r'allowUpload'] = this.allowUpload; - } else { - // json[r'allowUpload'] = null; + if (this.allowDownload.isPresent) { + final value = this.allowDownload.value; + json[r'allowDownload'] = value; } - json[r'assetIds'] = this.assetIds; - if (this.description != null) { - json[r'description'] = this.description; - } else { - // json[r'description'] = null; + if (this.allowUpload.isPresent) { + final value = this.allowUpload.value; + json[r'allowUpload'] = value; } - if (this.expiresAt != null) { - json[r'expiresAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.expiresAt!.millisecondsSinceEpoch - : this.expiresAt!.toUtc().toIso8601String(); - } else { - // json[r'expiresAt'] = null; + if (this.assetIds.isPresent) { + final value = this.assetIds.value; + json[r'assetIds'] = value; } - if (this.password != null) { - json[r'password'] = this.password; - } else { - // json[r'password'] = null; + if (this.description.isPresent) { + final value = this.description.value; + json[r'description'] = value; } - json[r'showMetadata'] = this.showMetadata; - if (this.slug != null) { - json[r'slug'] = this.slug; - } else { - // json[r'slug'] = null; + if (this.expiresAt.isPresent) { + final value = this.expiresAt.value; + json[r'expiresAt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); + } + if (this.password.isPresent) { + final value = this.password.value; + json[r'password'] = value; + } + if (this.showMetadata.isPresent) { + final value = this.showMetadata.value; + json[r'showMetadata'] = value; + } + if (this.slug.isPresent) { + final value = this.slug.value; + json[r'slug'] = value; } json[r'type'] = this.type; return json; @@ -146,17 +149,17 @@ class SharedLinkCreateDto { final json = value.cast(); return SharedLinkCreateDto( - albumId: mapValueOfType(json, r'albumId'), - allowDownload: mapValueOfType(json, r'allowDownload') ?? true, - allowUpload: mapValueOfType(json, r'allowUpload'), - assetIds: json[r'assetIds'] is Iterable + albumId: json.containsKey(r'albumId') ? Optional.present(mapValueOfType(json, r'albumId')) : const Optional.absent(), + allowDownload: json.containsKey(r'allowDownload') ? Optional.present(mapValueOfType(json, r'allowDownload')) : const Optional.absent(), + allowUpload: json.containsKey(r'allowUpload') ? Optional.present(mapValueOfType(json, r'allowUpload')) : const Optional.absent(), + assetIds: json.containsKey(r'assetIds') ? Optional.present(json[r'assetIds'] is Iterable ? (json[r'assetIds'] as Iterable).cast().toList(growable: false) - : const [], - description: mapValueOfType(json, r'description'), - expiresAt: mapDateTime(json, r'expiresAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - password: mapValueOfType(json, r'password'), - showMetadata: mapValueOfType(json, r'showMetadata') ?? true, - slug: mapValueOfType(json, r'slug'), + : const []) : const Optional.absent(), + description: json.containsKey(r'description') ? Optional.present(mapValueOfType(json, r'description')) : const Optional.absent(), + expiresAt: json.containsKey(r'expiresAt') ? Optional.present(mapDateTime(json, r'expiresAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + password: json.containsKey(r'password') ? Optional.present(mapValueOfType(json, r'password')) : const Optional.absent(), + showMetadata: json.containsKey(r'showMetadata') ? Optional.present(mapValueOfType(json, r'showMetadata')) : const Optional.absent(), + slug: json.containsKey(r'slug') ? Optional.present(mapValueOfType(json, r'slug')) : const Optional.absent(), type: SharedLinkType.fromJson(json[r'type'])!, ); } diff --git a/mobile/openapi/lib/model/shared_link_edit_dto.dart b/mobile/openapi/lib/model/shared_link_edit_dto.dart index 11d6cdd52e..a38d28b79b 100644 --- a/mobile/openapi/lib/model/shared_link_edit_dto.dart +++ b/mobile/openapi/lib/model/shared_link_edit_dto.dart @@ -13,14 +13,13 @@ part of openapi.api; class SharedLinkEditDto { /// Returns a new [SharedLinkEditDto] instance. SharedLinkEditDto({ - this.allowDownload, - this.allowUpload, - this.changeExpiryTime, - this.description, - this.expiresAt, - this.password, - this.showMetadata, - this.slug, + this.allowDownload = const Optional.absent(), + this.allowUpload = const Optional.absent(), + this.description = const Optional.absent(), + this.expiresAt = const Optional.absent(), + this.password = const Optional.absent(), + this.showMetadata = const Optional.absent(), + this.slug = const Optional.absent(), }); /// Allow downloads @@ -30,7 +29,7 @@ class SharedLinkEditDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? allowDownload; + Optional allowDownload; /// Allow uploads /// @@ -39,25 +38,16 @@ class SharedLinkEditDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? allowUpload; - - /// Whether to change the expiry time. Few clients cannot send null to set the expiryTime to never. Setting this flag and not sending expiryAt is considered as null instead. Clients that can send null values can ignore this. - /// - /// Please note: This property should have been non-nullable! Since the specification file - /// does not include a default value (using the "default:" property), however, the generated - /// source code must fall back to having a nullable type. - /// Consider adding a "default:" property in the specification file to hide this note. - /// - bool? changeExpiryTime; + Optional allowUpload; /// Link description - String? description; + Optional description; /// Expiration date - DateTime? expiresAt; + Optional expiresAt; /// Link password - String? password; + Optional password; /// Show metadata /// @@ -66,16 +56,15 @@ class SharedLinkEditDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? showMetadata; + Optional showMetadata; /// Custom URL slug - String? slug; + Optional slug; @override bool operator ==(Object other) => identical(this, other) || other is SharedLinkEditDto && other.allowDownload == allowDownload && other.allowUpload == allowUpload && - other.changeExpiryTime == changeExpiryTime && other.description == description && other.expiresAt == expiresAt && other.password == password && @@ -87,7 +76,6 @@ class SharedLinkEditDto { // ignore: unnecessary_parenthesis (allowDownload == null ? 0 : allowDownload!.hashCode) + (allowUpload == null ? 0 : allowUpload!.hashCode) + - (changeExpiryTime == null ? 0 : changeExpiryTime!.hashCode) + (description == null ? 0 : description!.hashCode) + (expiresAt == null ? 0 : expiresAt!.hashCode) + (password == null ? 0 : password!.hashCode) + @@ -95,51 +83,39 @@ class SharedLinkEditDto { (slug == null ? 0 : slug!.hashCode); @override - String toString() => 'SharedLinkEditDto[allowDownload=$allowDownload, allowUpload=$allowUpload, changeExpiryTime=$changeExpiryTime, description=$description, expiresAt=$expiresAt, password=$password, showMetadata=$showMetadata, slug=$slug]'; + String toString() => 'SharedLinkEditDto[allowDownload=$allowDownload, allowUpload=$allowUpload, description=$description, expiresAt=$expiresAt, password=$password, showMetadata=$showMetadata, slug=$slug]'; Map toJson() { final json = {}; - if (this.allowDownload != null) { - json[r'allowDownload'] = this.allowDownload; - } else { - // json[r'allowDownload'] = null; + if (this.allowDownload.isPresent) { + final value = this.allowDownload.value; + json[r'allowDownload'] = value; } - if (this.allowUpload != null) { - json[r'allowUpload'] = this.allowUpload; - } else { - // json[r'allowUpload'] = null; + if (this.allowUpload.isPresent) { + final value = this.allowUpload.value; + json[r'allowUpload'] = value; } - if (this.changeExpiryTime != null) { - json[r'changeExpiryTime'] = this.changeExpiryTime; - } else { - // json[r'changeExpiryTime'] = null; + if (this.description.isPresent) { + final value = this.description.value; + json[r'description'] = value; } - if (this.description != null) { - json[r'description'] = this.description; - } else { - // json[r'description'] = null; + if (this.expiresAt.isPresent) { + final value = this.expiresAt.value; + json[r'expiresAt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.expiresAt != null) { - json[r'expiresAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.expiresAt!.millisecondsSinceEpoch - : this.expiresAt!.toUtc().toIso8601String(); - } else { - // json[r'expiresAt'] = null; + if (this.password.isPresent) { + final value = this.password.value; + json[r'password'] = value; } - if (this.password != null) { - json[r'password'] = this.password; - } else { - // json[r'password'] = null; + if (this.showMetadata.isPresent) { + final value = this.showMetadata.value; + json[r'showMetadata'] = value; } - if (this.showMetadata != null) { - json[r'showMetadata'] = this.showMetadata; - } else { - // json[r'showMetadata'] = null; - } - if (this.slug != null) { - json[r'slug'] = this.slug; - } else { - // json[r'slug'] = null; + if (this.slug.isPresent) { + final value = this.slug.value; + json[r'slug'] = value; } return json; } @@ -153,14 +129,13 @@ class SharedLinkEditDto { final json = value.cast(); return SharedLinkEditDto( - allowDownload: mapValueOfType(json, r'allowDownload'), - allowUpload: mapValueOfType(json, r'allowUpload'), - changeExpiryTime: mapValueOfType(json, r'changeExpiryTime'), - description: mapValueOfType(json, r'description'), - expiresAt: mapDateTime(json, r'expiresAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - password: mapValueOfType(json, r'password'), - showMetadata: mapValueOfType(json, r'showMetadata'), - slug: mapValueOfType(json, r'slug'), + allowDownload: json.containsKey(r'allowDownload') ? Optional.present(mapValueOfType(json, r'allowDownload')) : const Optional.absent(), + allowUpload: json.containsKey(r'allowUpload') ? Optional.present(mapValueOfType(json, r'allowUpload')) : const Optional.absent(), + description: json.containsKey(r'description') ? Optional.present(mapValueOfType(json, r'description')) : const Optional.absent(), + expiresAt: json.containsKey(r'expiresAt') ? Optional.present(mapDateTime(json, r'expiresAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + password: json.containsKey(r'password') ? Optional.present(mapValueOfType(json, r'password')) : const Optional.absent(), + showMetadata: json.containsKey(r'showMetadata') ? Optional.present(mapValueOfType(json, r'showMetadata')) : const Optional.absent(), + slug: json.containsKey(r'slug') ? Optional.present(mapValueOfType(json, r'slug')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/shared_link_response_dto.dart b/mobile/openapi/lib/model/shared_link_response_dto.dart index bad0966ca2..8d7cb74eca 100644 --- a/mobile/openapi/lib/model/shared_link_response_dto.dart +++ b/mobile/openapi/lib/model/shared_link_response_dto.dart @@ -13,7 +13,7 @@ part of openapi.api; class SharedLinkResponseDto { /// Returns a new [SharedLinkResponseDto] instance. SharedLinkResponseDto({ - this.album, + this.album = const Optional.absent(), required this.allowDownload, required this.allowUpload, this.assets = const [], @@ -35,7 +35,7 @@ class SharedLinkResponseDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - AlbumResponseDto? album; + Optional album; /// Allow downloads bool allowDownload; @@ -114,10 +114,9 @@ class SharedLinkResponseDto { Map toJson() { final json = {}; - if (this.album != null) { - json[r'album'] = this.album; - } else { - // json[r'album'] = null; + if (this.album.isPresent) { + final value = this.album.value; + json[r'album'] = value; } json[r'allowDownload'] = this.allowDownload; json[r'allowUpload'] = this.allowUpload; @@ -128,27 +127,27 @@ class SharedLinkResponseDto { if (this.description != null) { json[r'description'] = this.description; } else { - // json[r'description'] = null; + json[r'description'] = null; } if (this.expiresAt != null) { json[r'expiresAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') ? this.expiresAt!.millisecondsSinceEpoch : this.expiresAt!.toUtc().toIso8601String(); } else { - // json[r'expiresAt'] = null; + json[r'expiresAt'] = null; } json[r'id'] = this.id; json[r'key'] = this.key; if (this.password != null) { json[r'password'] = this.password; } else { - // json[r'password'] = null; + json[r'password'] = null; } json[r'showMetadata'] = this.showMetadata; if (this.slug != null) { json[r'slug'] = this.slug; } else { - // json[r'slug'] = null; + json[r'slug'] = null; } json[r'type'] = this.type; json[r'userId'] = this.userId; @@ -164,7 +163,7 @@ class SharedLinkResponseDto { final json = value.cast(); return SharedLinkResponseDto( - album: AlbumResponseDto.fromJson(json[r'album']), + album: json.containsKey(r'album') ? Optional.present(AlbumResponseDto.fromJson(json[r'album'])) : const Optional.absent(), allowDownload: mapValueOfType(json, r'allowDownload')!, allowUpload: mapValueOfType(json, r'allowUpload')!, assets: AssetResponseDto.listFromJson(json[r'assets']), diff --git a/mobile/openapi/lib/model/shared_links_update.dart b/mobile/openapi/lib/model/shared_links_update.dart index 8e792b4f49..7c5761e343 100644 --- a/mobile/openapi/lib/model/shared_links_update.dart +++ b/mobile/openapi/lib/model/shared_links_update.dart @@ -13,8 +13,8 @@ part of openapi.api; class SharedLinksUpdate { /// Returns a new [SharedLinksUpdate] instance. SharedLinksUpdate({ - this.enabled, - this.sidebarWeb, + this.enabled = const Optional.absent(), + this.sidebarWeb = const Optional.absent(), }); /// Whether shared links are enabled @@ -24,7 +24,7 @@ class SharedLinksUpdate { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? enabled; + Optional enabled; /// Whether shared links appear in web sidebar /// @@ -33,7 +33,7 @@ class SharedLinksUpdate { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? sidebarWeb; + Optional sidebarWeb; @override bool operator ==(Object other) => identical(this, other) || other is SharedLinksUpdate && @@ -51,15 +51,13 @@ class SharedLinksUpdate { Map toJson() { final json = {}; - if (this.enabled != null) { - json[r'enabled'] = this.enabled; - } else { - // json[r'enabled'] = null; + if (this.enabled.isPresent) { + final value = this.enabled.value; + json[r'enabled'] = value; } - if (this.sidebarWeb != null) { - json[r'sidebarWeb'] = this.sidebarWeb; - } else { - // json[r'sidebarWeb'] = null; + if (this.sidebarWeb.isPresent) { + final value = this.sidebarWeb.value; + json[r'sidebarWeb'] = value; } return json; } @@ -73,8 +71,8 @@ class SharedLinksUpdate { final json = value.cast(); return SharedLinksUpdate( - enabled: mapValueOfType(json, r'enabled'), - sidebarWeb: mapValueOfType(json, r'sidebarWeb'), + enabled: json.containsKey(r'enabled') ? Optional.present(mapValueOfType(json, r'enabled')) : const Optional.absent(), + sidebarWeb: json.containsKey(r'sidebarWeb') ? Optional.present(mapValueOfType(json, r'sidebarWeb')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/smart_search_dto.dart b/mobile/openapi/lib/model/smart_search_dto.dart index 9bbb4a25f0..aff0030102 100644 --- a/mobile/openapi/lib/model/smart_search_dto.dart +++ b/mobile/openapi/lib/model/smart_search_dto.dart @@ -13,50 +13,50 @@ part of openapi.api; class SmartSearchDto { /// Returns a new [SmartSearchDto] instance. SmartSearchDto({ - this.albumIds = const [], - this.city, - this.country, - this.createdAfter, - this.createdBefore, - this.isEncoded, - this.isFavorite, - this.isMotion, - this.isNotInAlbum, - this.isOffline, - this.language, - this.lensModel, - this.libraryId, - this.make, - this.model, - this.ocr, - this.page, - this.personIds = const [], - this.query, - this.queryAssetId, - this.rating, - this.size, - this.state, - this.tagIds = const [], - this.takenAfter, - this.takenBefore, - this.trashedAfter, - this.trashedBefore, - this.type, - this.updatedAfter, - this.updatedBefore, - this.visibility, - this.withDeleted, - this.withExif, + this.albumIds = const Optional.present(const []), + this.city = const Optional.absent(), + this.country = const Optional.absent(), + this.createdAfter = const Optional.absent(), + this.createdBefore = const Optional.absent(), + this.isEncoded = const Optional.absent(), + this.isFavorite = const Optional.absent(), + this.isMotion = const Optional.absent(), + this.isNotInAlbum = const Optional.absent(), + this.isOffline = const Optional.absent(), + this.language = const Optional.absent(), + this.lensModel = const Optional.absent(), + this.libraryId = const Optional.absent(), + this.make = const Optional.absent(), + this.model = const Optional.absent(), + this.ocr = const Optional.absent(), + this.page = const Optional.absent(), + this.personIds = const Optional.present(const []), + this.query = const Optional.absent(), + this.queryAssetId = const Optional.absent(), + this.rating = const Optional.absent(), + this.size = const Optional.absent(), + this.state = const Optional.absent(), + this.tagIds = const Optional.present(const []), + this.takenAfter = const Optional.absent(), + this.takenBefore = const Optional.absent(), + this.trashedAfter = const Optional.absent(), + this.trashedBefore = const Optional.absent(), + this.type = const Optional.absent(), + this.updatedAfter = const Optional.absent(), + this.updatedBefore = const Optional.absent(), + this.visibility = const Optional.absent(), + this.withDeleted = const Optional.absent(), + this.withExif = const Optional.absent(), }); /// Filter by album IDs - List albumIds; + Optional?> albumIds; /// Filter by city name - String? city; + Optional city; /// Filter by country name - String? country; + Optional country; /// Filter by creation date (after) /// @@ -65,7 +65,7 @@ class SmartSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - DateTime? createdAfter; + Optional createdAfter; /// Filter by creation date (before) /// @@ -74,7 +74,7 @@ class SmartSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - DateTime? createdBefore; + Optional createdBefore; /// Filter by encoded status /// @@ -83,7 +83,7 @@ class SmartSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? isEncoded; + Optional isEncoded; /// Filter by favorite status /// @@ -92,7 +92,7 @@ class SmartSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? isFavorite; + Optional isFavorite; /// Filter by motion photo status /// @@ -101,7 +101,7 @@ class SmartSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? isMotion; + Optional isMotion; /// Filter assets not in any album /// @@ -110,7 +110,7 @@ class SmartSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? isNotInAlbum; + Optional isNotInAlbum; /// Filter by offline status /// @@ -119,7 +119,7 @@ class SmartSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? isOffline; + Optional isOffline; /// Search language code /// @@ -128,19 +128,19 @@ class SmartSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? language; + Optional language; /// Filter by lens model - String? lensModel; + Optional lensModel; /// Library ID to filter by - String? libraryId; + Optional libraryId; /// Filter by camera make - String? make; + Optional make; /// Filter by camera model - String? model; + Optional model; /// Filter by OCR text content /// @@ -149,7 +149,7 @@ class SmartSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? ocr; + Optional ocr; /// Page number /// @@ -161,10 +161,10 @@ class SmartSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - int? page; + Optional page; /// Filter by person IDs - List personIds; + Optional?> personIds; /// Natural language search query /// @@ -173,7 +173,7 @@ class SmartSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? query; + Optional query; /// Asset ID to use as search reference /// @@ -182,13 +182,13 @@ class SmartSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? queryAssetId; + Optional queryAssetId; /// Filter by rating [1-5], or null for unrated /// /// Minimum value: -1 /// Maximum value: 5 - int? rating; + Optional rating; /// Number of results to return /// @@ -200,13 +200,13 @@ class SmartSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - int? size; + Optional size; /// Filter by state/province name - String? state; + Optional state; /// Filter by tag IDs - List? tagIds; + Optional?> tagIds; /// Filter by taken date (after) /// @@ -215,7 +215,7 @@ class SmartSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - DateTime? takenAfter; + Optional takenAfter; /// Filter by taken date (before) /// @@ -224,7 +224,7 @@ class SmartSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - DateTime? takenBefore; + Optional takenBefore; /// Filter by trash date (after) /// @@ -233,7 +233,7 @@ class SmartSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - DateTime? trashedAfter; + Optional trashedAfter; /// Filter by trash date (before) /// @@ -242,7 +242,7 @@ class SmartSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - DateTime? trashedBefore; + Optional trashedBefore; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -250,7 +250,7 @@ class SmartSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - AssetTypeEnum? type; + Optional type; /// Filter by update date (after) /// @@ -259,7 +259,7 @@ class SmartSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - DateTime? updatedAfter; + Optional updatedAfter; /// Filter by update date (before) /// @@ -268,7 +268,7 @@ class SmartSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - DateTime? updatedBefore; + Optional updatedBefore; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -276,7 +276,7 @@ class SmartSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - AssetVisibility? visibility; + Optional visibility; /// Include deleted assets /// @@ -285,7 +285,7 @@ class SmartSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? withDeleted; + Optional withDeleted; /// Include EXIF data in response /// @@ -294,7 +294,7 @@ class SmartSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? withExif; + Optional withExif; @override bool operator ==(Object other) => identical(this, other) || other is SmartSearchDto && @@ -376,183 +376,157 @@ class SmartSearchDto { Map toJson() { final json = {}; - json[r'albumIds'] = this.albumIds; - if (this.city != null) { - json[r'city'] = this.city; - } else { - // json[r'city'] = null; + if (this.albumIds.isPresent) { + final value = this.albumIds.value; + json[r'albumIds'] = value; } - if (this.country != null) { - json[r'country'] = this.country; - } else { - // json[r'country'] = null; + if (this.city.isPresent) { + final value = this.city.value; + json[r'city'] = value; } - if (this.createdAfter != null) { - json[r'createdAfter'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.createdAfter!.millisecondsSinceEpoch - : this.createdAfter!.toUtc().toIso8601String(); - } else { - // json[r'createdAfter'] = null; + if (this.country.isPresent) { + final value = this.country.value; + json[r'country'] = value; } - if (this.createdBefore != null) { - json[r'createdBefore'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.createdBefore!.millisecondsSinceEpoch - : this.createdBefore!.toUtc().toIso8601String(); - } else { - // json[r'createdBefore'] = null; + if (this.createdAfter.isPresent) { + final value = this.createdAfter.value; + json[r'createdAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.isEncoded != null) { - json[r'isEncoded'] = this.isEncoded; - } else { - // json[r'isEncoded'] = null; + if (this.createdBefore.isPresent) { + final value = this.createdBefore.value; + json[r'createdBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.isFavorite != null) { - json[r'isFavorite'] = this.isFavorite; - } else { - // json[r'isFavorite'] = null; + if (this.isEncoded.isPresent) { + final value = this.isEncoded.value; + json[r'isEncoded'] = value; } - if (this.isMotion != null) { - json[r'isMotion'] = this.isMotion; - } else { - // json[r'isMotion'] = null; + if (this.isFavorite.isPresent) { + final value = this.isFavorite.value; + json[r'isFavorite'] = value; } - if (this.isNotInAlbum != null) { - json[r'isNotInAlbum'] = this.isNotInAlbum; - } else { - // json[r'isNotInAlbum'] = null; + if (this.isMotion.isPresent) { + final value = this.isMotion.value; + json[r'isMotion'] = value; } - if (this.isOffline != null) { - json[r'isOffline'] = this.isOffline; - } else { - // json[r'isOffline'] = null; + if (this.isNotInAlbum.isPresent) { + final value = this.isNotInAlbum.value; + json[r'isNotInAlbum'] = value; } - if (this.language != null) { - json[r'language'] = this.language; - } else { - // json[r'language'] = null; + if (this.isOffline.isPresent) { + final value = this.isOffline.value; + json[r'isOffline'] = value; } - if (this.lensModel != null) { - json[r'lensModel'] = this.lensModel; - } else { - // json[r'lensModel'] = null; + if (this.language.isPresent) { + final value = this.language.value; + json[r'language'] = value; } - if (this.libraryId != null) { - json[r'libraryId'] = this.libraryId; - } else { - // json[r'libraryId'] = null; + if (this.lensModel.isPresent) { + final value = this.lensModel.value; + json[r'lensModel'] = value; } - if (this.make != null) { - json[r'make'] = this.make; - } else { - // json[r'make'] = null; + if (this.libraryId.isPresent) { + final value = this.libraryId.value; + json[r'libraryId'] = value; } - if (this.model != null) { - json[r'model'] = this.model; - } else { - // json[r'model'] = null; + if (this.make.isPresent) { + final value = this.make.value; + json[r'make'] = value; } - if (this.ocr != null) { - json[r'ocr'] = this.ocr; - } else { - // json[r'ocr'] = null; + if (this.model.isPresent) { + final value = this.model.value; + json[r'model'] = value; } - if (this.page != null) { - json[r'page'] = this.page; - } else { - // json[r'page'] = null; + if (this.ocr.isPresent) { + final value = this.ocr.value; + json[r'ocr'] = value; } - json[r'personIds'] = this.personIds; - if (this.query != null) { - json[r'query'] = this.query; - } else { - // json[r'query'] = null; + if (this.page.isPresent) { + final value = this.page.value; + json[r'page'] = value; } - if (this.queryAssetId != null) { - json[r'queryAssetId'] = this.queryAssetId; - } else { - // json[r'queryAssetId'] = null; + if (this.personIds.isPresent) { + final value = this.personIds.value; + json[r'personIds'] = value; } - if (this.rating != null) { - json[r'rating'] = this.rating; - } else { - // json[r'rating'] = null; + if (this.query.isPresent) { + final value = this.query.value; + json[r'query'] = value; } - if (this.size != null) { - json[r'size'] = this.size; - } else { - // json[r'size'] = null; + if (this.queryAssetId.isPresent) { + final value = this.queryAssetId.value; + json[r'queryAssetId'] = value; } - if (this.state != null) { - json[r'state'] = this.state; - } else { - // json[r'state'] = null; + if (this.rating.isPresent) { + final value = this.rating.value; + json[r'rating'] = value; } - if (this.tagIds != null) { - json[r'tagIds'] = this.tagIds; - } else { - // json[r'tagIds'] = null; + if (this.size.isPresent) { + final value = this.size.value; + json[r'size'] = value; } - if (this.takenAfter != null) { - json[r'takenAfter'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.takenAfter!.millisecondsSinceEpoch - : this.takenAfter!.toUtc().toIso8601String(); - } else { - // json[r'takenAfter'] = null; + if (this.state.isPresent) { + final value = this.state.value; + json[r'state'] = value; } - if (this.takenBefore != null) { - json[r'takenBefore'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.takenBefore!.millisecondsSinceEpoch - : this.takenBefore!.toUtc().toIso8601String(); - } else { - // json[r'takenBefore'] = null; + if (this.tagIds.isPresent) { + final value = this.tagIds.value; + json[r'tagIds'] = value; } - if (this.trashedAfter != null) { - json[r'trashedAfter'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.trashedAfter!.millisecondsSinceEpoch - : this.trashedAfter!.toUtc().toIso8601String(); - } else { - // json[r'trashedAfter'] = null; + if (this.takenAfter.isPresent) { + final value = this.takenAfter.value; + json[r'takenAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.trashedBefore != null) { - json[r'trashedBefore'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.trashedBefore!.millisecondsSinceEpoch - : this.trashedBefore!.toUtc().toIso8601String(); - } else { - // json[r'trashedBefore'] = null; + if (this.takenBefore.isPresent) { + final value = this.takenBefore.value; + json[r'takenBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.type != null) { - json[r'type'] = this.type; - } else { - // json[r'type'] = null; + if (this.trashedAfter.isPresent) { + final value = this.trashedAfter.value; + json[r'trashedAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.updatedAfter != null) { - json[r'updatedAfter'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.updatedAfter!.millisecondsSinceEpoch - : this.updatedAfter!.toUtc().toIso8601String(); - } else { - // json[r'updatedAfter'] = null; + if (this.trashedBefore.isPresent) { + final value = this.trashedBefore.value; + json[r'trashedBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.updatedBefore != null) { - json[r'updatedBefore'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.updatedBefore!.millisecondsSinceEpoch - : this.updatedBefore!.toUtc().toIso8601String(); - } else { - // json[r'updatedBefore'] = null; + if (this.type.isPresent) { + final value = this.type.value; + json[r'type'] = value; } - if (this.visibility != null) { - json[r'visibility'] = this.visibility; - } else { - // json[r'visibility'] = null; + if (this.updatedAfter.isPresent) { + final value = this.updatedAfter.value; + json[r'updatedAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.withDeleted != null) { - json[r'withDeleted'] = this.withDeleted; - } else { - // json[r'withDeleted'] = null; + if (this.updatedBefore.isPresent) { + final value = this.updatedBefore.value; + json[r'updatedBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.withExif != null) { - json[r'withExif'] = this.withExif; - } else { - // json[r'withExif'] = null; + if (this.visibility.isPresent) { + final value = this.visibility.value; + json[r'visibility'] = value; + } + if (this.withDeleted.isPresent) { + final value = this.withDeleted.value; + json[r'withDeleted'] = value; + } + if (this.withExif.isPresent) { + final value = this.withExif.value; + json[r'withExif'] = value; } return json; } @@ -566,46 +540,46 @@ class SmartSearchDto { final json = value.cast(); return SmartSearchDto( - albumIds: json[r'albumIds'] is Iterable + albumIds: json.containsKey(r'albumIds') ? Optional.present(json[r'albumIds'] is Iterable ? (json[r'albumIds'] as Iterable).cast().toList(growable: false) - : const [], - city: mapValueOfType(json, r'city'), - country: mapValueOfType(json, r'country'), - createdAfter: mapDateTime(json, r'createdAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - createdBefore: mapDateTime(json, r'createdBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - isEncoded: mapValueOfType(json, r'isEncoded'), - isFavorite: mapValueOfType(json, r'isFavorite'), - isMotion: mapValueOfType(json, r'isMotion'), - isNotInAlbum: mapValueOfType(json, r'isNotInAlbum'), - isOffline: mapValueOfType(json, r'isOffline'), - language: mapValueOfType(json, r'language'), - lensModel: mapValueOfType(json, r'lensModel'), - libraryId: mapValueOfType(json, r'libraryId'), - make: mapValueOfType(json, r'make'), - model: mapValueOfType(json, r'model'), - ocr: mapValueOfType(json, r'ocr'), - page: mapValueOfType(json, r'page'), - personIds: json[r'personIds'] is Iterable + : const []) : const Optional.absent(), + city: json.containsKey(r'city') ? Optional.present(mapValueOfType(json, r'city')) : const Optional.absent(), + country: json.containsKey(r'country') ? Optional.present(mapValueOfType(json, r'country')) : const Optional.absent(), + createdAfter: json.containsKey(r'createdAfter') ? Optional.present(mapDateTime(json, r'createdAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + createdBefore: json.containsKey(r'createdBefore') ? Optional.present(mapDateTime(json, r'createdBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + isEncoded: json.containsKey(r'isEncoded') ? Optional.present(mapValueOfType(json, r'isEncoded')) : const Optional.absent(), + isFavorite: json.containsKey(r'isFavorite') ? Optional.present(mapValueOfType(json, r'isFavorite')) : const Optional.absent(), + isMotion: json.containsKey(r'isMotion') ? Optional.present(mapValueOfType(json, r'isMotion')) : const Optional.absent(), + isNotInAlbum: json.containsKey(r'isNotInAlbum') ? Optional.present(mapValueOfType(json, r'isNotInAlbum')) : const Optional.absent(), + isOffline: json.containsKey(r'isOffline') ? Optional.present(mapValueOfType(json, r'isOffline')) : const Optional.absent(), + language: json.containsKey(r'language') ? Optional.present(mapValueOfType(json, r'language')) : const Optional.absent(), + lensModel: json.containsKey(r'lensModel') ? Optional.present(mapValueOfType(json, r'lensModel')) : const Optional.absent(), + libraryId: json.containsKey(r'libraryId') ? Optional.present(mapValueOfType(json, r'libraryId')) : const Optional.absent(), + make: json.containsKey(r'make') ? Optional.present(mapValueOfType(json, r'make')) : const Optional.absent(), + model: json.containsKey(r'model') ? Optional.present(mapValueOfType(json, r'model')) : const Optional.absent(), + ocr: json.containsKey(r'ocr') ? Optional.present(mapValueOfType(json, r'ocr')) : const Optional.absent(), + page: json.containsKey(r'page') ? Optional.present(json[r'page'] == null ? null : int.parse('${json[r'page']}')) : const Optional.absent(), + personIds: json.containsKey(r'personIds') ? Optional.present(json[r'personIds'] is Iterable ? (json[r'personIds'] as Iterable).cast().toList(growable: false) - : const [], - query: mapValueOfType(json, r'query'), - queryAssetId: mapValueOfType(json, r'queryAssetId'), - rating: mapValueOfType(json, r'rating'), - size: mapValueOfType(json, r'size'), - state: mapValueOfType(json, r'state'), - tagIds: json[r'tagIds'] is Iterable + : const []) : const Optional.absent(), + query: json.containsKey(r'query') ? Optional.present(mapValueOfType(json, r'query')) : const Optional.absent(), + queryAssetId: json.containsKey(r'queryAssetId') ? Optional.present(mapValueOfType(json, r'queryAssetId')) : const Optional.absent(), + rating: json.containsKey(r'rating') ? Optional.present(json[r'rating'] == null ? null : int.parse('${json[r'rating']}')) : const Optional.absent(), + size: json.containsKey(r'size') ? Optional.present(json[r'size'] == null ? null : int.parse('${json[r'size']}')) : const Optional.absent(), + state: json.containsKey(r'state') ? Optional.present(mapValueOfType(json, r'state')) : const Optional.absent(), + tagIds: json.containsKey(r'tagIds') ? Optional.present(json[r'tagIds'] is Iterable ? (json[r'tagIds'] as Iterable).cast().toList(growable: false) - : const [], - takenAfter: mapDateTime(json, r'takenAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - takenBefore: mapDateTime(json, r'takenBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - trashedAfter: mapDateTime(json, r'trashedAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - trashedBefore: mapDateTime(json, r'trashedBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - type: AssetTypeEnum.fromJson(json[r'type']), - updatedAfter: mapDateTime(json, r'updatedAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - updatedBefore: mapDateTime(json, r'updatedBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - visibility: AssetVisibility.fromJson(json[r'visibility']), - withDeleted: mapValueOfType(json, r'withDeleted'), - withExif: mapValueOfType(json, r'withExif'), + : const []) : const Optional.absent(), + takenAfter: json.containsKey(r'takenAfter') ? Optional.present(mapDateTime(json, r'takenAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + takenBefore: json.containsKey(r'takenBefore') ? Optional.present(mapDateTime(json, r'takenBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + trashedAfter: json.containsKey(r'trashedAfter') ? Optional.present(mapDateTime(json, r'trashedAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + trashedBefore: json.containsKey(r'trashedBefore') ? Optional.present(mapDateTime(json, r'trashedBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + type: json.containsKey(r'type') ? Optional.present(AssetTypeEnum.fromJson(json[r'type'])) : const Optional.absent(), + updatedAfter: json.containsKey(r'updatedAfter') ? Optional.present(mapDateTime(json, r'updatedAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + updatedBefore: json.containsKey(r'updatedBefore') ? Optional.present(mapDateTime(json, r'updatedBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + visibility: json.containsKey(r'visibility') ? Optional.present(AssetVisibility.fromJson(json[r'visibility'])) : const Optional.absent(), + withDeleted: json.containsKey(r'withDeleted') ? Optional.present(mapValueOfType(json, r'withDeleted')) : const Optional.absent(), + withExif: json.containsKey(r'withExif') ? Optional.present(mapValueOfType(json, r'withExif')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/stack_update_dto.dart b/mobile/openapi/lib/model/stack_update_dto.dart index e81c204f97..98787f3a43 100644 --- a/mobile/openapi/lib/model/stack_update_dto.dart +++ b/mobile/openapi/lib/model/stack_update_dto.dart @@ -13,7 +13,7 @@ part of openapi.api; class StackUpdateDto { /// Returns a new [StackUpdateDto] instance. StackUpdateDto({ - this.primaryAssetId, + this.primaryAssetId = const Optional.absent(), }); /// Primary asset ID @@ -23,7 +23,7 @@ class StackUpdateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? primaryAssetId; + Optional primaryAssetId; @override bool operator ==(Object other) => identical(this, other) || other is StackUpdateDto && @@ -39,10 +39,9 @@ class StackUpdateDto { Map toJson() { final json = {}; - if (this.primaryAssetId != null) { - json[r'primaryAssetId'] = this.primaryAssetId; - } else { - // json[r'primaryAssetId'] = null; + if (this.primaryAssetId.isPresent) { + final value = this.primaryAssetId.value; + json[r'primaryAssetId'] = value; } return json; } @@ -56,7 +55,7 @@ class StackUpdateDto { final json = value.cast(); return StackUpdateDto( - primaryAssetId: mapValueOfType(json, r'primaryAssetId'), + primaryAssetId: json.containsKey(r'primaryAssetId') ? Optional.present(mapValueOfType(json, r'primaryAssetId')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/statistics_search_dto.dart b/mobile/openapi/lib/model/statistics_search_dto.dart index f276e3717b..5b88d136bb 100644 --- a/mobile/openapi/lib/model/statistics_search_dto.dart +++ b/mobile/openapi/lib/model/statistics_search_dto.dart @@ -13,44 +13,44 @@ part of openapi.api; class StatisticsSearchDto { /// Returns a new [StatisticsSearchDto] instance. StatisticsSearchDto({ - this.albumIds = const [], - this.city, - this.country, - this.createdAfter, - this.createdBefore, - this.description, - this.isEncoded, - this.isFavorite, - this.isMotion, - this.isNotInAlbum, - this.isOffline, - this.lensModel, - this.libraryId, - this.make, - this.model, - this.ocr, - this.personIds = const [], - this.rating, - this.state, - this.tagIds = const [], - this.takenAfter, - this.takenBefore, - this.trashedAfter, - this.trashedBefore, - this.type, - this.updatedAfter, - this.updatedBefore, - this.visibility, + this.albumIds = const Optional.present(const []), + this.city = const Optional.absent(), + this.country = const Optional.absent(), + this.createdAfter = const Optional.absent(), + this.createdBefore = const Optional.absent(), + this.description = const Optional.absent(), + this.isEncoded = const Optional.absent(), + this.isFavorite = const Optional.absent(), + this.isMotion = const Optional.absent(), + this.isNotInAlbum = const Optional.absent(), + this.isOffline = const Optional.absent(), + this.lensModel = const Optional.absent(), + this.libraryId = const Optional.absent(), + this.make = const Optional.absent(), + this.model = const Optional.absent(), + this.ocr = const Optional.absent(), + this.personIds = const Optional.present(const []), + this.rating = const Optional.absent(), + this.state = const Optional.absent(), + this.tagIds = const Optional.present(const []), + this.takenAfter = const Optional.absent(), + this.takenBefore = const Optional.absent(), + this.trashedAfter = const Optional.absent(), + this.trashedBefore = const Optional.absent(), + this.type = const Optional.absent(), + this.updatedAfter = const Optional.absent(), + this.updatedBefore = const Optional.absent(), + this.visibility = const Optional.absent(), }); /// Filter by album IDs - List albumIds; + Optional?> albumIds; /// Filter by city name - String? city; + Optional city; /// Filter by country name - String? country; + Optional country; /// Filter by creation date (after) /// @@ -59,7 +59,7 @@ class StatisticsSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - DateTime? createdAfter; + Optional createdAfter; /// Filter by creation date (before) /// @@ -68,7 +68,7 @@ class StatisticsSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - DateTime? createdBefore; + Optional createdBefore; /// Filter by description text /// @@ -77,7 +77,7 @@ class StatisticsSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? description; + Optional description; /// Filter by encoded status /// @@ -86,7 +86,7 @@ class StatisticsSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? isEncoded; + Optional isEncoded; /// Filter by favorite status /// @@ -95,7 +95,7 @@ class StatisticsSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? isFavorite; + Optional isFavorite; /// Filter by motion photo status /// @@ -104,7 +104,7 @@ class StatisticsSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? isMotion; + Optional isMotion; /// Filter assets not in any album /// @@ -113,7 +113,7 @@ class StatisticsSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? isNotInAlbum; + Optional isNotInAlbum; /// Filter by offline status /// @@ -122,19 +122,19 @@ class StatisticsSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? isOffline; + Optional isOffline; /// Filter by lens model - String? lensModel; + Optional lensModel; /// Library ID to filter by - String? libraryId; + Optional libraryId; /// Filter by camera make - String? make; + Optional make; /// Filter by camera model - String? model; + Optional model; /// Filter by OCR text content /// @@ -143,22 +143,22 @@ class StatisticsSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? ocr; + Optional ocr; /// Filter by person IDs - List personIds; + Optional?> personIds; /// Filter by rating [1-5], or null for unrated /// /// Minimum value: -1 /// Maximum value: 5 - int? rating; + Optional rating; /// Filter by state/province name - String? state; + Optional state; /// Filter by tag IDs - List? tagIds; + Optional?> tagIds; /// Filter by taken date (after) /// @@ -167,7 +167,7 @@ class StatisticsSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - DateTime? takenAfter; + Optional takenAfter; /// Filter by taken date (before) /// @@ -176,7 +176,7 @@ class StatisticsSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - DateTime? takenBefore; + Optional takenBefore; /// Filter by trash date (after) /// @@ -185,7 +185,7 @@ class StatisticsSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - DateTime? trashedAfter; + Optional trashedAfter; /// Filter by trash date (before) /// @@ -194,7 +194,7 @@ class StatisticsSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - DateTime? trashedBefore; + Optional trashedBefore; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -202,7 +202,7 @@ class StatisticsSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - AssetTypeEnum? type; + Optional type; /// Filter by update date (after) /// @@ -211,7 +211,7 @@ class StatisticsSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - DateTime? updatedAfter; + Optional updatedAfter; /// Filter by update date (before) /// @@ -220,7 +220,7 @@ class StatisticsSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - DateTime? updatedBefore; + Optional updatedBefore; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -228,7 +228,7 @@ class StatisticsSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - AssetVisibility? visibility; + Optional visibility; @override bool operator ==(Object other) => identical(this, other) || other is StatisticsSearchDto && @@ -298,153 +298,133 @@ class StatisticsSearchDto { Map toJson() { final json = {}; - json[r'albumIds'] = this.albumIds; - if (this.city != null) { - json[r'city'] = this.city; - } else { - // json[r'city'] = null; + if (this.albumIds.isPresent) { + final value = this.albumIds.value; + json[r'albumIds'] = value; } - if (this.country != null) { - json[r'country'] = this.country; - } else { - // json[r'country'] = null; + if (this.city.isPresent) { + final value = this.city.value; + json[r'city'] = value; } - if (this.createdAfter != null) { - json[r'createdAfter'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.createdAfter!.millisecondsSinceEpoch - : this.createdAfter!.toUtc().toIso8601String(); - } else { - // json[r'createdAfter'] = null; + if (this.country.isPresent) { + final value = this.country.value; + json[r'country'] = value; } - if (this.createdBefore != null) { - json[r'createdBefore'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.createdBefore!.millisecondsSinceEpoch - : this.createdBefore!.toUtc().toIso8601String(); - } else { - // json[r'createdBefore'] = null; + if (this.createdAfter.isPresent) { + final value = this.createdAfter.value; + json[r'createdAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.description != null) { - json[r'description'] = this.description; - } else { - // json[r'description'] = null; + if (this.createdBefore.isPresent) { + final value = this.createdBefore.value; + json[r'createdBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.isEncoded != null) { - json[r'isEncoded'] = this.isEncoded; - } else { - // json[r'isEncoded'] = null; + if (this.description.isPresent) { + final value = this.description.value; + json[r'description'] = value; } - if (this.isFavorite != null) { - json[r'isFavorite'] = this.isFavorite; - } else { - // json[r'isFavorite'] = null; + if (this.isEncoded.isPresent) { + final value = this.isEncoded.value; + json[r'isEncoded'] = value; } - if (this.isMotion != null) { - json[r'isMotion'] = this.isMotion; - } else { - // json[r'isMotion'] = null; + if (this.isFavorite.isPresent) { + final value = this.isFavorite.value; + json[r'isFavorite'] = value; } - if (this.isNotInAlbum != null) { - json[r'isNotInAlbum'] = this.isNotInAlbum; - } else { - // json[r'isNotInAlbum'] = null; + if (this.isMotion.isPresent) { + final value = this.isMotion.value; + json[r'isMotion'] = value; } - if (this.isOffline != null) { - json[r'isOffline'] = this.isOffline; - } else { - // json[r'isOffline'] = null; + if (this.isNotInAlbum.isPresent) { + final value = this.isNotInAlbum.value; + json[r'isNotInAlbum'] = value; } - if (this.lensModel != null) { - json[r'lensModel'] = this.lensModel; - } else { - // json[r'lensModel'] = null; + if (this.isOffline.isPresent) { + final value = this.isOffline.value; + json[r'isOffline'] = value; } - if (this.libraryId != null) { - json[r'libraryId'] = this.libraryId; - } else { - // json[r'libraryId'] = null; + if (this.lensModel.isPresent) { + final value = this.lensModel.value; + json[r'lensModel'] = value; } - if (this.make != null) { - json[r'make'] = this.make; - } else { - // json[r'make'] = null; + if (this.libraryId.isPresent) { + final value = this.libraryId.value; + json[r'libraryId'] = value; } - if (this.model != null) { - json[r'model'] = this.model; - } else { - // json[r'model'] = null; + if (this.make.isPresent) { + final value = this.make.value; + json[r'make'] = value; } - if (this.ocr != null) { - json[r'ocr'] = this.ocr; - } else { - // json[r'ocr'] = null; + if (this.model.isPresent) { + final value = this.model.value; + json[r'model'] = value; } - json[r'personIds'] = this.personIds; - if (this.rating != null) { - json[r'rating'] = this.rating; - } else { - // json[r'rating'] = null; + if (this.ocr.isPresent) { + final value = this.ocr.value; + json[r'ocr'] = value; } - if (this.state != null) { - json[r'state'] = this.state; - } else { - // json[r'state'] = null; + if (this.personIds.isPresent) { + final value = this.personIds.value; + json[r'personIds'] = value; } - if (this.tagIds != null) { - json[r'tagIds'] = this.tagIds; - } else { - // json[r'tagIds'] = null; + if (this.rating.isPresent) { + final value = this.rating.value; + json[r'rating'] = value; } - if (this.takenAfter != null) { - json[r'takenAfter'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.takenAfter!.millisecondsSinceEpoch - : this.takenAfter!.toUtc().toIso8601String(); - } else { - // json[r'takenAfter'] = null; + if (this.state.isPresent) { + final value = this.state.value; + json[r'state'] = value; } - if (this.takenBefore != null) { - json[r'takenBefore'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.takenBefore!.millisecondsSinceEpoch - : this.takenBefore!.toUtc().toIso8601String(); - } else { - // json[r'takenBefore'] = null; + if (this.tagIds.isPresent) { + final value = this.tagIds.value; + json[r'tagIds'] = value; } - if (this.trashedAfter != null) { - json[r'trashedAfter'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.trashedAfter!.millisecondsSinceEpoch - : this.trashedAfter!.toUtc().toIso8601String(); - } else { - // json[r'trashedAfter'] = null; + if (this.takenAfter.isPresent) { + final value = this.takenAfter.value; + json[r'takenAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.trashedBefore != null) { - json[r'trashedBefore'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.trashedBefore!.millisecondsSinceEpoch - : this.trashedBefore!.toUtc().toIso8601String(); - } else { - // json[r'trashedBefore'] = null; + if (this.takenBefore.isPresent) { + final value = this.takenBefore.value; + json[r'takenBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.type != null) { - json[r'type'] = this.type; - } else { - // json[r'type'] = null; + if (this.trashedAfter.isPresent) { + final value = this.trashedAfter.value; + json[r'trashedAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.updatedAfter != null) { - json[r'updatedAfter'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.updatedAfter!.millisecondsSinceEpoch - : this.updatedAfter!.toUtc().toIso8601String(); - } else { - // json[r'updatedAfter'] = null; + if (this.trashedBefore.isPresent) { + final value = this.trashedBefore.value; + json[r'trashedBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.updatedBefore != null) { - json[r'updatedBefore'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.updatedBefore!.millisecondsSinceEpoch - : this.updatedBefore!.toUtc().toIso8601String(); - } else { - // json[r'updatedBefore'] = null; + if (this.type.isPresent) { + final value = this.type.value; + json[r'type'] = value; } - if (this.visibility != null) { - json[r'visibility'] = this.visibility; - } else { - // json[r'visibility'] = null; + if (this.updatedAfter.isPresent) { + final value = this.updatedAfter.value; + json[r'updatedAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); + } + if (this.updatedBefore.isPresent) { + final value = this.updatedBefore.value; + json[r'updatedBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); + } + if (this.visibility.isPresent) { + final value = this.visibility.value; + json[r'visibility'] = value; } return json; } @@ -458,40 +438,40 @@ class StatisticsSearchDto { final json = value.cast(); return StatisticsSearchDto( - albumIds: json[r'albumIds'] is Iterable + albumIds: json.containsKey(r'albumIds') ? Optional.present(json[r'albumIds'] is Iterable ? (json[r'albumIds'] as Iterable).cast().toList(growable: false) - : const [], - city: mapValueOfType(json, r'city'), - country: mapValueOfType(json, r'country'), - createdAfter: mapDateTime(json, r'createdAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - createdBefore: mapDateTime(json, r'createdBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - description: mapValueOfType(json, r'description'), - isEncoded: mapValueOfType(json, r'isEncoded'), - isFavorite: mapValueOfType(json, r'isFavorite'), - isMotion: mapValueOfType(json, r'isMotion'), - isNotInAlbum: mapValueOfType(json, r'isNotInAlbum'), - isOffline: mapValueOfType(json, r'isOffline'), - lensModel: mapValueOfType(json, r'lensModel'), - libraryId: mapValueOfType(json, r'libraryId'), - make: mapValueOfType(json, r'make'), - model: mapValueOfType(json, r'model'), - ocr: mapValueOfType(json, r'ocr'), - personIds: json[r'personIds'] is Iterable + : const []) : const Optional.absent(), + city: json.containsKey(r'city') ? Optional.present(mapValueOfType(json, r'city')) : const Optional.absent(), + country: json.containsKey(r'country') ? Optional.present(mapValueOfType(json, r'country')) : const Optional.absent(), + createdAfter: json.containsKey(r'createdAfter') ? Optional.present(mapDateTime(json, r'createdAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + createdBefore: json.containsKey(r'createdBefore') ? Optional.present(mapDateTime(json, r'createdBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + description: json.containsKey(r'description') ? Optional.present(mapValueOfType(json, r'description')) : const Optional.absent(), + isEncoded: json.containsKey(r'isEncoded') ? Optional.present(mapValueOfType(json, r'isEncoded')) : const Optional.absent(), + isFavorite: json.containsKey(r'isFavorite') ? Optional.present(mapValueOfType(json, r'isFavorite')) : const Optional.absent(), + isMotion: json.containsKey(r'isMotion') ? Optional.present(mapValueOfType(json, r'isMotion')) : const Optional.absent(), + isNotInAlbum: json.containsKey(r'isNotInAlbum') ? Optional.present(mapValueOfType(json, r'isNotInAlbum')) : const Optional.absent(), + isOffline: json.containsKey(r'isOffline') ? Optional.present(mapValueOfType(json, r'isOffline')) : const Optional.absent(), + lensModel: json.containsKey(r'lensModel') ? Optional.present(mapValueOfType(json, r'lensModel')) : const Optional.absent(), + libraryId: json.containsKey(r'libraryId') ? Optional.present(mapValueOfType(json, r'libraryId')) : const Optional.absent(), + make: json.containsKey(r'make') ? Optional.present(mapValueOfType(json, r'make')) : const Optional.absent(), + model: json.containsKey(r'model') ? Optional.present(mapValueOfType(json, r'model')) : const Optional.absent(), + ocr: json.containsKey(r'ocr') ? Optional.present(mapValueOfType(json, r'ocr')) : const Optional.absent(), + personIds: json.containsKey(r'personIds') ? Optional.present(json[r'personIds'] is Iterable ? (json[r'personIds'] as Iterable).cast().toList(growable: false) - : const [], - rating: mapValueOfType(json, r'rating'), - state: mapValueOfType(json, r'state'), - tagIds: json[r'tagIds'] is Iterable + : const []) : const Optional.absent(), + rating: json.containsKey(r'rating') ? Optional.present(json[r'rating'] == null ? null : int.parse('${json[r'rating']}')) : const Optional.absent(), + state: json.containsKey(r'state') ? Optional.present(mapValueOfType(json, r'state')) : const Optional.absent(), + tagIds: json.containsKey(r'tagIds') ? Optional.present(json[r'tagIds'] is Iterable ? (json[r'tagIds'] as Iterable).cast().toList(growable: false) - : const [], - takenAfter: mapDateTime(json, r'takenAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - takenBefore: mapDateTime(json, r'takenBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - trashedAfter: mapDateTime(json, r'trashedAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - trashedBefore: mapDateTime(json, r'trashedBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - type: AssetTypeEnum.fromJson(json[r'type']), - updatedAfter: mapDateTime(json, r'updatedAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - updatedBefore: mapDateTime(json, r'updatedBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - visibility: AssetVisibility.fromJson(json[r'visibility']), + : const []) : const Optional.absent(), + takenAfter: json.containsKey(r'takenAfter') ? Optional.present(mapDateTime(json, r'takenAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + takenBefore: json.containsKey(r'takenBefore') ? Optional.present(mapDateTime(json, r'takenBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + trashedAfter: json.containsKey(r'trashedAfter') ? Optional.present(mapDateTime(json, r'trashedAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + trashedBefore: json.containsKey(r'trashedBefore') ? Optional.present(mapDateTime(json, r'trashedBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + type: json.containsKey(r'type') ? Optional.present(AssetTypeEnum.fromJson(json[r'type'])) : const Optional.absent(), + updatedAfter: json.containsKey(r'updatedAfter') ? Optional.present(mapDateTime(json, r'updatedAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + updatedBefore: json.containsKey(r'updatedBefore') ? Optional.present(mapDateTime(json, r'updatedBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + visibility: json.containsKey(r'visibility') ? Optional.present(AssetVisibility.fromJson(json[r'visibility'])) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/sync_ack_delete_dto.dart b/mobile/openapi/lib/model/sync_ack_delete_dto.dart index b72ae8c5a6..76e2b780a7 100644 --- a/mobile/openapi/lib/model/sync_ack_delete_dto.dart +++ b/mobile/openapi/lib/model/sync_ack_delete_dto.dart @@ -13,11 +13,11 @@ part of openapi.api; class SyncAckDeleteDto { /// Returns a new [SyncAckDeleteDto] instance. SyncAckDeleteDto({ - this.types = const [], + this.types = const Optional.present(const []), }); /// Sync entity types to delete acks for - List types; + Optional?> types; @override bool operator ==(Object other) => identical(this, other) || other is SyncAckDeleteDto && @@ -33,7 +33,10 @@ class SyncAckDeleteDto { Map toJson() { final json = {}; - json[r'types'] = this.types; + if (this.types.isPresent) { + final value = this.types.value; + json[r'types'] = value; + } return json; } @@ -46,7 +49,7 @@ class SyncAckDeleteDto { final json = value.cast(); return SyncAckDeleteDto( - types: SyncEntityType.listFromJson(json[r'types']), + types: json.containsKey(r'types') ? Optional.present(SyncEntityType.listFromJson(json[r'types'])) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/sync_album_v1.dart b/mobile/openapi/lib/model/sync_album_v1.dart index 17b2bda02b..0a216cd93d 100644 --- a/mobile/openapi/lib/model/sync_album_v1.dart +++ b/mobile/openapi/lib/model/sync_album_v1.dart @@ -92,7 +92,7 @@ class SyncAlbumV1 { if (this.thumbnailAssetId != null) { json[r'thumbnailAssetId'] = this.thumbnailAssetId; } else { - // json[r'thumbnailAssetId'] = null; + json[r'thumbnailAssetId'] = null; } json[r'updatedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') ? this.updatedAt.millisecondsSinceEpoch diff --git a/mobile/openapi/lib/model/sync_album_v2.dart b/mobile/openapi/lib/model/sync_album_v2.dart index 67c65a190b..c0d0cf339b 100644 --- a/mobile/openapi/lib/model/sync_album_v2.dart +++ b/mobile/openapi/lib/model/sync_album_v2.dart @@ -85,7 +85,7 @@ class SyncAlbumV2 { if (this.thumbnailAssetId != null) { json[r'thumbnailAssetId'] = this.thumbnailAssetId; } else { - // json[r'thumbnailAssetId'] = null; + json[r'thumbnailAssetId'] = null; } json[r'updatedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') ? this.updatedAt.millisecondsSinceEpoch diff --git a/mobile/openapi/lib/model/sync_asset_exif_v1.dart b/mobile/openapi/lib/model/sync_asset_exif_v1.dart index caaeed7fb3..fb806322c9 100644 --- a/mobile/openapi/lib/model/sync_asset_exif_v1.dart +++ b/mobile/openapi/lib/model/sync_asset_exif_v1.dart @@ -196,126 +196,126 @@ class SyncAssetExifV1 { if (this.city != null) { json[r'city'] = this.city; } else { - // json[r'city'] = null; + json[r'city'] = null; } if (this.country != null) { json[r'country'] = this.country; } else { - // json[r'country'] = null; + json[r'country'] = null; } if (this.dateTimeOriginal != null) { json[r'dateTimeOriginal'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') ? this.dateTimeOriginal!.millisecondsSinceEpoch : this.dateTimeOriginal!.toUtc().toIso8601String(); } else { - // json[r'dateTimeOriginal'] = null; + json[r'dateTimeOriginal'] = null; } if (this.description != null) { json[r'description'] = this.description; } else { - // json[r'description'] = null; + json[r'description'] = null; } if (this.exifImageHeight != null) { json[r'exifImageHeight'] = this.exifImageHeight; } else { - // json[r'exifImageHeight'] = null; + json[r'exifImageHeight'] = null; } if (this.exifImageWidth != null) { json[r'exifImageWidth'] = this.exifImageWidth; } else { - // json[r'exifImageWidth'] = null; + json[r'exifImageWidth'] = null; } if (this.exposureTime != null) { json[r'exposureTime'] = this.exposureTime; } else { - // json[r'exposureTime'] = null; + json[r'exposureTime'] = null; } if (this.fNumber != null) { json[r'fNumber'] = this.fNumber; } else { - // json[r'fNumber'] = null; + json[r'fNumber'] = null; } if (this.fileSizeInByte != null) { json[r'fileSizeInByte'] = this.fileSizeInByte; } else { - // json[r'fileSizeInByte'] = null; + json[r'fileSizeInByte'] = null; } if (this.focalLength != null) { json[r'focalLength'] = this.focalLength; } else { - // json[r'focalLength'] = null; + json[r'focalLength'] = null; } if (this.fps != null) { json[r'fps'] = this.fps; } else { - // json[r'fps'] = null; + json[r'fps'] = null; } if (this.iso != null) { json[r'iso'] = this.iso; } else { - // json[r'iso'] = null; + json[r'iso'] = null; } if (this.latitude != null) { json[r'latitude'] = this.latitude; } else { - // json[r'latitude'] = null; + json[r'latitude'] = null; } if (this.lensModel != null) { json[r'lensModel'] = this.lensModel; } else { - // json[r'lensModel'] = null; + json[r'lensModel'] = null; } if (this.longitude != null) { json[r'longitude'] = this.longitude; } else { - // json[r'longitude'] = null; + json[r'longitude'] = null; } if (this.make != null) { json[r'make'] = this.make; } else { - // json[r'make'] = null; + json[r'make'] = null; } if (this.model != null) { json[r'model'] = this.model; } else { - // json[r'model'] = null; + json[r'model'] = null; } if (this.modifyDate != null) { json[r'modifyDate'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') ? this.modifyDate!.millisecondsSinceEpoch : this.modifyDate!.toUtc().toIso8601String(); } else { - // json[r'modifyDate'] = null; + json[r'modifyDate'] = null; } if (this.orientation != null) { json[r'orientation'] = this.orientation; } else { - // json[r'orientation'] = null; + json[r'orientation'] = null; } if (this.profileDescription != null) { json[r'profileDescription'] = this.profileDescription; } else { - // json[r'profileDescription'] = null; + json[r'profileDescription'] = null; } if (this.projectionType != null) { json[r'projectionType'] = this.projectionType; } else { - // json[r'projectionType'] = null; + json[r'projectionType'] = null; } if (this.rating != null) { json[r'rating'] = this.rating; } else { - // json[r'rating'] = null; + json[r'rating'] = null; } if (this.state != null) { json[r'state'] = this.state; } else { - // json[r'state'] = null; + json[r'state'] = null; } if (this.timeZone != null) { json[r'timeZone'] = this.timeZone; } else { - // json[r'timeZone'] = null; + json[r'timeZone'] = null; } return json; } @@ -337,14 +337,14 @@ class SyncAssetExifV1 { exifImageHeight: mapValueOfType(json, r'exifImageHeight'), exifImageWidth: mapValueOfType(json, r'exifImageWidth'), exposureTime: mapValueOfType(json, r'exposureTime'), - fNumber: (mapValueOfType(json, r'fNumber'))?.toDouble(), + fNumber: mapValueOfType(json, r'fNumber'), fileSizeInByte: mapValueOfType(json, r'fileSizeInByte'), - focalLength: (mapValueOfType(json, r'focalLength'))?.toDouble(), - fps: (mapValueOfType(json, r'fps'))?.toDouble(), + focalLength: mapValueOfType(json, r'focalLength'), + fps: mapValueOfType(json, r'fps'), iso: mapValueOfType(json, r'iso'), - latitude: (mapValueOfType(json, r'latitude'))?.toDouble(), + latitude: mapValueOfType(json, r'latitude'), lensModel: mapValueOfType(json, r'lensModel'), - longitude: (mapValueOfType(json, r'longitude'))?.toDouble(), + longitude: mapValueOfType(json, r'longitude'), make: mapValueOfType(json, r'make'), model: mapValueOfType(json, r'model'), modifyDate: mapDateTime(json, r'modifyDate', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), diff --git a/mobile/openapi/lib/model/sync_asset_face_v1.dart b/mobile/openapi/lib/model/sync_asset_face_v1.dart index c3f74ff2cd..7ccc455f47 100644 --- a/mobile/openapi/lib/model/sync_asset_face_v1.dart +++ b/mobile/openapi/lib/model/sync_asset_face_v1.dart @@ -116,7 +116,7 @@ class SyncAssetFaceV1 { if (this.personId != null) { json[r'personId'] = this.personId; } else { - // json[r'personId'] = null; + json[r'personId'] = null; } json[r'sourceType'] = this.sourceType; return json; diff --git a/mobile/openapi/lib/model/sync_asset_face_v2.dart b/mobile/openapi/lib/model/sync_asset_face_v2.dart index aeefc2ece9..aa5f016176 100644 --- a/mobile/openapi/lib/model/sync_asset_face_v2.dart +++ b/mobile/openapi/lib/model/sync_asset_face_v2.dart @@ -127,7 +127,7 @@ class SyncAssetFaceV2 { ? this.deletedAt!.millisecondsSinceEpoch : this.deletedAt!.toUtc().toIso8601String(); } else { - // json[r'deletedAt'] = null; + json[r'deletedAt'] = null; } json[r'id'] = this.id; json[r'imageHeight'] = this.imageHeight; @@ -136,7 +136,7 @@ class SyncAssetFaceV2 { if (this.personId != null) { json[r'personId'] = this.personId; } else { - // json[r'personId'] = null; + json[r'personId'] = null; } json[r'sourceType'] = this.sourceType; return json; diff --git a/mobile/openapi/lib/model/sync_asset_ocr_v1.dart b/mobile/openapi/lib/model/sync_asset_ocr_v1.dart index d6d1e97962..616583189a 100644 --- a/mobile/openapi/lib/model/sync_asset_ocr_v1.dart +++ b/mobile/openapi/lib/model/sync_asset_ocr_v1.dart @@ -138,19 +138,19 @@ class SyncAssetOcrV1 { return SyncAssetOcrV1( assetId: mapValueOfType(json, r'assetId')!, - boxScore: (mapValueOfType(json, r'boxScore')!).toDouble(), + boxScore: mapValueOfType(json, r'boxScore')!, id: mapValueOfType(json, r'id')!, isVisible: mapValueOfType(json, r'isVisible')!, text: mapValueOfType(json, r'text')!, - textScore: (mapValueOfType(json, r'textScore')!).toDouble(), - x1: (mapValueOfType(json, r'x1')!).toDouble(), - x2: (mapValueOfType(json, r'x2')!).toDouble(), - x3: (mapValueOfType(json, r'x3')!).toDouble(), - x4: (mapValueOfType(json, r'x4')!).toDouble(), - y1: (mapValueOfType(json, r'y1')!).toDouble(), - y2: (mapValueOfType(json, r'y2')!).toDouble(), - y3: (mapValueOfType(json, r'y3')!).toDouble(), - y4: (mapValueOfType(json, r'y4')!).toDouble(), + textScore: mapValueOfType(json, r'textScore')!, + x1: mapValueOfType(json, r'x1')!, + x2: mapValueOfType(json, r'x2')!, + x3: mapValueOfType(json, r'x3')!, + x4: mapValueOfType(json, r'x4')!, + y1: mapValueOfType(json, r'y1')!, + y2: mapValueOfType(json, r'y2')!, + y3: mapValueOfType(json, r'y3')!, + y4: mapValueOfType(json, r'y4')!, ); } return null; diff --git a/mobile/openapi/lib/model/sync_asset_v1.dart b/mobile/openapi/lib/model/sync_asset_v1.dart index d08de6ab72..a9b8ca30cb 100644 --- a/mobile/openapi/lib/model/sync_asset_v1.dart +++ b/mobile/openapi/lib/model/sync_asset_v1.dart @@ -14,6 +14,7 @@ class SyncAssetV1 { /// Returns a new [SyncAssetV1] instance. SyncAssetV1({ required this.checksum, + required this.createdAt, required this.deletedAt, required this.duration, required this.fileCreatedAt, @@ -37,6 +38,9 @@ class SyncAssetV1 { /// Checksum String checksum; + /// Uploaded to Immich at + DateTime? createdAt; + /// Deleted at DateTime? deletedAt; @@ -98,6 +102,7 @@ class SyncAssetV1 { @override bool operator ==(Object other) => identical(this, other) || other is SyncAssetV1 && other.checksum == checksum && + other.createdAt == createdAt && other.deletedAt == deletedAt && other.duration == duration && other.fileCreatedAt == fileCreatedAt && @@ -121,6 +126,7 @@ class SyncAssetV1 { int get hashCode => // ignore: unnecessary_parenthesis (checksum.hashCode) + + (createdAt == null ? 0 : createdAt!.hashCode) + (deletedAt == null ? 0 : deletedAt!.hashCode) + (duration == null ? 0 : duration!.hashCode) + (fileCreatedAt == null ? 0 : fileCreatedAt!.hashCode) + @@ -141,41 +147,48 @@ class SyncAssetV1 { (width == null ? 0 : width!.hashCode); @override - String toString() => 'SyncAssetV1[checksum=$checksum, deletedAt=$deletedAt, duration=$duration, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, height=$height, id=$id, isEdited=$isEdited, isFavorite=$isFavorite, libraryId=$libraryId, livePhotoVideoId=$livePhotoVideoId, localDateTime=$localDateTime, originalFileName=$originalFileName, ownerId=$ownerId, stackId=$stackId, thumbhash=$thumbhash, type=$type, visibility=$visibility, width=$width]'; + String toString() => 'SyncAssetV1[checksum=$checksum, createdAt=$createdAt, deletedAt=$deletedAt, duration=$duration, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, height=$height, id=$id, isEdited=$isEdited, isFavorite=$isFavorite, libraryId=$libraryId, livePhotoVideoId=$livePhotoVideoId, localDateTime=$localDateTime, originalFileName=$originalFileName, ownerId=$ownerId, stackId=$stackId, thumbhash=$thumbhash, type=$type, visibility=$visibility, width=$width]'; Map toJson() { final json = {}; json[r'checksum'] = this.checksum; + if (this.createdAt != null) { + json[r'createdAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? this.createdAt!.millisecondsSinceEpoch + : this.createdAt!.toUtc().toIso8601String(); + } else { + json[r'createdAt'] = null; + } if (this.deletedAt != null) { json[r'deletedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') ? this.deletedAt!.millisecondsSinceEpoch : this.deletedAt!.toUtc().toIso8601String(); } else { - // json[r'deletedAt'] = null; + json[r'deletedAt'] = null; } if (this.duration != null) { json[r'duration'] = this.duration; } else { - // json[r'duration'] = null; + json[r'duration'] = null; } if (this.fileCreatedAt != null) { json[r'fileCreatedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') ? this.fileCreatedAt!.millisecondsSinceEpoch : this.fileCreatedAt!.toUtc().toIso8601String(); } else { - // json[r'fileCreatedAt'] = null; + json[r'fileCreatedAt'] = null; } if (this.fileModifiedAt != null) { json[r'fileModifiedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') ? this.fileModifiedAt!.millisecondsSinceEpoch : this.fileModifiedAt!.toUtc().toIso8601String(); } else { - // json[r'fileModifiedAt'] = null; + json[r'fileModifiedAt'] = null; } if (this.height != null) { json[r'height'] = this.height; } else { - // json[r'height'] = null; + json[r'height'] = null; } json[r'id'] = this.id; json[r'isEdited'] = this.isEdited; @@ -183,38 +196,38 @@ class SyncAssetV1 { if (this.libraryId != null) { json[r'libraryId'] = this.libraryId; } else { - // json[r'libraryId'] = null; + json[r'libraryId'] = null; } if (this.livePhotoVideoId != null) { json[r'livePhotoVideoId'] = this.livePhotoVideoId; } else { - // json[r'livePhotoVideoId'] = null; + json[r'livePhotoVideoId'] = null; } if (this.localDateTime != null) { json[r'localDateTime'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') ? this.localDateTime!.millisecondsSinceEpoch : this.localDateTime!.toUtc().toIso8601String(); } else { - // json[r'localDateTime'] = null; + json[r'localDateTime'] = null; } json[r'originalFileName'] = this.originalFileName; json[r'ownerId'] = this.ownerId; if (this.stackId != null) { json[r'stackId'] = this.stackId; } else { - // json[r'stackId'] = null; + json[r'stackId'] = null; } if (this.thumbhash != null) { json[r'thumbhash'] = this.thumbhash; } else { - // json[r'thumbhash'] = null; + json[r'thumbhash'] = null; } json[r'type'] = this.type; json[r'visibility'] = this.visibility; if (this.width != null) { json[r'width'] = this.width; } else { - // json[r'width'] = null; + json[r'width'] = null; } return json; } @@ -229,6 +242,7 @@ class SyncAssetV1 { return SyncAssetV1( checksum: mapValueOfType(json, r'checksum')!, + createdAt: mapDateTime(json, r'createdAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), deletedAt: mapDateTime(json, r'deletedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), duration: mapValueOfType(json, r'duration'), fileCreatedAt: mapDateTime(json, r'fileCreatedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), @@ -295,6 +309,7 @@ class SyncAssetV1 { /// The list of required keys that must be present in a JSON. static const requiredKeys = { 'checksum', + 'createdAt', 'deletedAt', 'duration', 'fileCreatedAt', diff --git a/mobile/openapi/lib/model/sync_asset_v2.dart b/mobile/openapi/lib/model/sync_asset_v2.dart index ebe75c59b2..987f8ded93 100644 --- a/mobile/openapi/lib/model/sync_asset_v2.dart +++ b/mobile/openapi/lib/model/sync_asset_v2.dart @@ -14,6 +14,7 @@ class SyncAssetV2 { /// Returns a new [SyncAssetV2] instance. SyncAssetV2({ required this.checksum, + required this.createdAt, required this.deletedAt, required this.duration, required this.fileCreatedAt, @@ -37,6 +38,9 @@ class SyncAssetV2 { /// Checksum String checksum; + /// Uploaded to Immich at + DateTime? createdAt; + /// Deleted at DateTime? deletedAt; @@ -101,6 +105,7 @@ class SyncAssetV2 { @override bool operator ==(Object other) => identical(this, other) || other is SyncAssetV2 && other.checksum == checksum && + other.createdAt == createdAt && other.deletedAt == deletedAt && other.duration == duration && other.fileCreatedAt == fileCreatedAt && @@ -124,6 +129,7 @@ class SyncAssetV2 { int get hashCode => // ignore: unnecessary_parenthesis (checksum.hashCode) + + (createdAt == null ? 0 : createdAt!.hashCode) + (deletedAt == null ? 0 : deletedAt!.hashCode) + (duration == null ? 0 : duration!.hashCode) + (fileCreatedAt == null ? 0 : fileCreatedAt!.hashCode) + @@ -144,41 +150,48 @@ class SyncAssetV2 { (width == null ? 0 : width!.hashCode); @override - String toString() => 'SyncAssetV2[checksum=$checksum, deletedAt=$deletedAt, duration=$duration, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, height=$height, id=$id, isEdited=$isEdited, isFavorite=$isFavorite, libraryId=$libraryId, livePhotoVideoId=$livePhotoVideoId, localDateTime=$localDateTime, originalFileName=$originalFileName, ownerId=$ownerId, stackId=$stackId, thumbhash=$thumbhash, type=$type, visibility=$visibility, width=$width]'; + String toString() => 'SyncAssetV2[checksum=$checksum, createdAt=$createdAt, deletedAt=$deletedAt, duration=$duration, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, height=$height, id=$id, isEdited=$isEdited, isFavorite=$isFavorite, libraryId=$libraryId, livePhotoVideoId=$livePhotoVideoId, localDateTime=$localDateTime, originalFileName=$originalFileName, ownerId=$ownerId, stackId=$stackId, thumbhash=$thumbhash, type=$type, visibility=$visibility, width=$width]'; Map toJson() { final json = {}; json[r'checksum'] = this.checksum; + if (this.createdAt != null) { + json[r'createdAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? this.createdAt!.millisecondsSinceEpoch + : this.createdAt!.toUtc().toIso8601String(); + } else { + json[r'createdAt'] = null; + } if (this.deletedAt != null) { json[r'deletedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') ? this.deletedAt!.millisecondsSinceEpoch : this.deletedAt!.toUtc().toIso8601String(); } else { - // json[r'deletedAt'] = null; + json[r'deletedAt'] = null; } if (this.duration != null) { json[r'duration'] = this.duration; } else { - // json[r'duration'] = null; + json[r'duration'] = null; } if (this.fileCreatedAt != null) { json[r'fileCreatedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') ? this.fileCreatedAt!.millisecondsSinceEpoch : this.fileCreatedAt!.toUtc().toIso8601String(); } else { - // json[r'fileCreatedAt'] = null; + json[r'fileCreatedAt'] = null; } if (this.fileModifiedAt != null) { json[r'fileModifiedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') ? this.fileModifiedAt!.millisecondsSinceEpoch : this.fileModifiedAt!.toUtc().toIso8601String(); } else { - // json[r'fileModifiedAt'] = null; + json[r'fileModifiedAt'] = null; } if (this.height != null) { json[r'height'] = this.height; } else { - // json[r'height'] = null; + json[r'height'] = null; } json[r'id'] = this.id; json[r'isEdited'] = this.isEdited; @@ -186,38 +199,38 @@ class SyncAssetV2 { if (this.libraryId != null) { json[r'libraryId'] = this.libraryId; } else { - // json[r'libraryId'] = null; + json[r'libraryId'] = null; } if (this.livePhotoVideoId != null) { json[r'livePhotoVideoId'] = this.livePhotoVideoId; } else { - // json[r'livePhotoVideoId'] = null; + json[r'livePhotoVideoId'] = null; } if (this.localDateTime != null) { json[r'localDateTime'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') ? this.localDateTime!.millisecondsSinceEpoch : this.localDateTime!.toUtc().toIso8601String(); } else { - // json[r'localDateTime'] = null; + json[r'localDateTime'] = null; } json[r'originalFileName'] = this.originalFileName; json[r'ownerId'] = this.ownerId; if (this.stackId != null) { json[r'stackId'] = this.stackId; } else { - // json[r'stackId'] = null; + json[r'stackId'] = null; } if (this.thumbhash != null) { json[r'thumbhash'] = this.thumbhash; } else { - // json[r'thumbhash'] = null; + json[r'thumbhash'] = null; } json[r'type'] = this.type; json[r'visibility'] = this.visibility; if (this.width != null) { json[r'width'] = this.width; } else { - // json[r'width'] = null; + json[r'width'] = null; } return json; } @@ -232,6 +245,7 @@ class SyncAssetV2 { return SyncAssetV2( checksum: mapValueOfType(json, r'checksum')!, + createdAt: mapDateTime(json, r'createdAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), deletedAt: mapDateTime(json, r'deletedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), duration: mapValueOfType(json, r'duration'), fileCreatedAt: mapDateTime(json, r'fileCreatedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), @@ -298,6 +312,7 @@ class SyncAssetV2 { /// The list of required keys that must be present in a JSON. static const requiredKeys = { 'checksum', + 'createdAt', 'deletedAt', 'duration', 'fileCreatedAt', diff --git a/mobile/openapi/lib/model/sync_auth_user_v1.dart b/mobile/openapi/lib/model/sync_auth_user_v1.dart index c64d82bfbd..0eac7cf9b4 100644 --- a/mobile/openapi/lib/model/sync_auth_user_v1.dart +++ b/mobile/openapi/lib/model/sync_auth_user_v1.dart @@ -13,7 +13,7 @@ part of openapi.api; class SyncAuthUserV1 { /// Returns a new [SyncAuthUserV1] instance. SyncAuthUserV1({ - this.avatarColor, + this.avatarColor = const Optional.absent(), required this.deletedAt, required this.email, required this.hasProfileImage, @@ -28,7 +28,7 @@ class SyncAuthUserV1 { required this.storageLabel, }); - UserAvatarColor? avatarColor; + Optional avatarColor; /// User deleted at DateTime? deletedAt; @@ -110,17 +110,16 @@ class SyncAuthUserV1 { Map toJson() { final json = {}; - if (this.avatarColor != null) { - json[r'avatarColor'] = this.avatarColor; - } else { - // json[r'avatarColor'] = null; + if (this.avatarColor.isPresent) { + final value = this.avatarColor.value; + json[r'avatarColor'] = value; } if (this.deletedAt != null) { json[r'deletedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') ? this.deletedAt!.millisecondsSinceEpoch : this.deletedAt!.toUtc().toIso8601String(); } else { - // json[r'deletedAt'] = null; + json[r'deletedAt'] = null; } json[r'email'] = this.email; json[r'hasProfileImage'] = this.hasProfileImage; @@ -131,7 +130,7 @@ class SyncAuthUserV1 { if (this.pinCode != null) { json[r'pinCode'] = this.pinCode; } else { - // json[r'pinCode'] = null; + json[r'pinCode'] = null; } json[r'profileChangedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') ? this.profileChangedAt.millisecondsSinceEpoch @@ -139,13 +138,13 @@ class SyncAuthUserV1 { if (this.quotaSizeInBytes != null) { json[r'quotaSizeInBytes'] = this.quotaSizeInBytes; } else { - // json[r'quotaSizeInBytes'] = null; + json[r'quotaSizeInBytes'] = null; } json[r'quotaUsageInBytes'] = this.quotaUsageInBytes; if (this.storageLabel != null) { json[r'storageLabel'] = this.storageLabel; } else { - // json[r'storageLabel'] = null; + json[r'storageLabel'] = null; } return json; } @@ -159,7 +158,7 @@ class SyncAuthUserV1 { final json = value.cast(); return SyncAuthUserV1( - avatarColor: UserAvatarColor.fromJson(json[r'avatarColor']), + avatarColor: json.containsKey(r'avatarColor') ? Optional.present(UserAvatarColor.fromJson(json[r'avatarColor'])) : const Optional.absent(), deletedAt: mapDateTime(json, r'deletedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), email: mapValueOfType(json, r'email')!, hasProfileImage: mapValueOfType(json, r'hasProfileImage')!, diff --git a/mobile/openapi/lib/model/sync_entity_type.dart b/mobile/openapi/lib/model/sync_entity_type.dart index cb7c6dba05..543e72eb44 100644 --- a/mobile/openapi/lib/model/sync_entity_type.dart +++ b/mobile/openapi/lib/model/sync_entity_type.dart @@ -27,6 +27,7 @@ class SyncEntityType { static const userV1 = SyncEntityType._(r'UserV1'); static const userDeleteV1 = SyncEntityType._(r'UserDeleteV1'); static const assetV1 = SyncEntityType._(r'AssetV1'); + static const assetV2 = SyncEntityType._(r'AssetV2'); static const assetDeleteV1 = SyncEntityType._(r'AssetDeleteV1'); static const assetExifV1 = SyncEntityType._(r'AssetExifV1'); static const assetEditV1 = SyncEntityType._(r'AssetEditV1'); @@ -38,7 +39,9 @@ class SyncEntityType { static const partnerV1 = SyncEntityType._(r'PartnerV1'); static const partnerDeleteV1 = SyncEntityType._(r'PartnerDeleteV1'); static const partnerAssetV1 = SyncEntityType._(r'PartnerAssetV1'); + static const partnerAssetV2 = SyncEntityType._(r'PartnerAssetV2'); static const partnerAssetBackfillV1 = SyncEntityType._(r'PartnerAssetBackfillV1'); + static const partnerAssetBackfillV2 = SyncEntityType._(r'PartnerAssetBackfillV2'); static const partnerAssetDeleteV1 = SyncEntityType._(r'PartnerAssetDeleteV1'); static const partnerAssetExifV1 = SyncEntityType._(r'PartnerAssetExifV1'); static const partnerAssetExifBackfillV1 = SyncEntityType._(r'PartnerAssetExifBackfillV1'); @@ -52,8 +55,11 @@ class SyncEntityType { static const albumUserBackfillV1 = SyncEntityType._(r'AlbumUserBackfillV1'); static const albumUserDeleteV1 = SyncEntityType._(r'AlbumUserDeleteV1'); static const albumAssetCreateV1 = SyncEntityType._(r'AlbumAssetCreateV1'); + static const albumAssetCreateV2 = SyncEntityType._(r'AlbumAssetCreateV2'); static const albumAssetUpdateV1 = SyncEntityType._(r'AlbumAssetUpdateV1'); + static const albumAssetUpdateV2 = SyncEntityType._(r'AlbumAssetUpdateV2'); static const albumAssetBackfillV1 = SyncEntityType._(r'AlbumAssetBackfillV1'); + static const albumAssetBackfillV2 = SyncEntityType._(r'AlbumAssetBackfillV2'); static const albumAssetExifCreateV1 = SyncEntityType._(r'AlbumAssetExifCreateV1'); static const albumAssetExifUpdateV1 = SyncEntityType._(r'AlbumAssetExifUpdateV1'); static const albumAssetExifBackfillV1 = SyncEntityType._(r'AlbumAssetExifBackfillV1'); @@ -83,6 +89,7 @@ class SyncEntityType { userV1, userDeleteV1, assetV1, + assetV2, assetDeleteV1, assetExifV1, assetEditV1, @@ -94,7 +101,9 @@ class SyncEntityType { partnerV1, partnerDeleteV1, partnerAssetV1, + partnerAssetV2, partnerAssetBackfillV1, + partnerAssetBackfillV2, partnerAssetDeleteV1, partnerAssetExifV1, partnerAssetExifBackfillV1, @@ -108,8 +117,11 @@ class SyncEntityType { albumUserBackfillV1, albumUserDeleteV1, albumAssetCreateV1, + albumAssetCreateV2, albumAssetUpdateV1, + albumAssetUpdateV2, albumAssetBackfillV1, + albumAssetBackfillV2, albumAssetExifCreateV1, albumAssetExifUpdateV1, albumAssetExifBackfillV1, @@ -174,6 +186,7 @@ class SyncEntityTypeTypeTransformer { case r'UserV1': return SyncEntityType.userV1; case r'UserDeleteV1': return SyncEntityType.userDeleteV1; case r'AssetV1': return SyncEntityType.assetV1; + case r'AssetV2': return SyncEntityType.assetV2; case r'AssetDeleteV1': return SyncEntityType.assetDeleteV1; case r'AssetExifV1': return SyncEntityType.assetExifV1; case r'AssetEditV1': return SyncEntityType.assetEditV1; @@ -185,7 +198,9 @@ class SyncEntityTypeTypeTransformer { case r'PartnerV1': return SyncEntityType.partnerV1; case r'PartnerDeleteV1': return SyncEntityType.partnerDeleteV1; case r'PartnerAssetV1': return SyncEntityType.partnerAssetV1; + case r'PartnerAssetV2': return SyncEntityType.partnerAssetV2; case r'PartnerAssetBackfillV1': return SyncEntityType.partnerAssetBackfillV1; + case r'PartnerAssetBackfillV2': return SyncEntityType.partnerAssetBackfillV2; case r'PartnerAssetDeleteV1': return SyncEntityType.partnerAssetDeleteV1; case r'PartnerAssetExifV1': return SyncEntityType.partnerAssetExifV1; case r'PartnerAssetExifBackfillV1': return SyncEntityType.partnerAssetExifBackfillV1; @@ -199,8 +214,11 @@ class SyncEntityTypeTypeTransformer { case r'AlbumUserBackfillV1': return SyncEntityType.albumUserBackfillV1; case r'AlbumUserDeleteV1': return SyncEntityType.albumUserDeleteV1; case r'AlbumAssetCreateV1': return SyncEntityType.albumAssetCreateV1; + case r'AlbumAssetCreateV2': return SyncEntityType.albumAssetCreateV2; case r'AlbumAssetUpdateV1': return SyncEntityType.albumAssetUpdateV1; + case r'AlbumAssetUpdateV2': return SyncEntityType.albumAssetUpdateV2; case r'AlbumAssetBackfillV1': return SyncEntityType.albumAssetBackfillV1; + case r'AlbumAssetBackfillV2': return SyncEntityType.albumAssetBackfillV2; case r'AlbumAssetExifCreateV1': return SyncEntityType.albumAssetExifCreateV1; case r'AlbumAssetExifUpdateV1': return SyncEntityType.albumAssetExifUpdateV1; case r'AlbumAssetExifBackfillV1': return SyncEntityType.albumAssetExifBackfillV1; diff --git a/mobile/openapi/lib/model/sync_memory_v1.dart b/mobile/openapi/lib/model/sync_memory_v1.dart index 855340f4d7..f78ad0e7ba 100644 --- a/mobile/openapi/lib/model/sync_memory_v1.dart +++ b/mobile/openapi/lib/model/sync_memory_v1.dart @@ -107,14 +107,14 @@ class SyncMemoryV1 { ? this.deletedAt!.millisecondsSinceEpoch : this.deletedAt!.toUtc().toIso8601String(); } else { - // json[r'deletedAt'] = null; + json[r'deletedAt'] = null; } if (this.hideAt != null) { json[r'hideAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') ? this.hideAt!.millisecondsSinceEpoch : this.hideAt!.toUtc().toIso8601String(); } else { - // json[r'hideAt'] = null; + json[r'hideAt'] = null; } json[r'id'] = this.id; json[r'isSaved'] = this.isSaved; @@ -127,14 +127,14 @@ class SyncMemoryV1 { ? this.seenAt!.millisecondsSinceEpoch : this.seenAt!.toUtc().toIso8601String(); } else { - // json[r'seenAt'] = null; + json[r'seenAt'] = null; } if (this.showAt != null) { json[r'showAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') ? this.showAt!.millisecondsSinceEpoch : this.showAt!.toUtc().toIso8601String(); } else { - // json[r'showAt'] = null; + json[r'showAt'] = null; } json[r'type'] = this.type; json[r'updatedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') diff --git a/mobile/openapi/lib/model/sync_person_v1.dart b/mobile/openapi/lib/model/sync_person_v1.dart index 1bd6f4a160..e79b7cfe16 100644 --- a/mobile/openapi/lib/model/sync_person_v1.dart +++ b/mobile/openapi/lib/model/sync_person_v1.dart @@ -92,12 +92,12 @@ class SyncPersonV1 { ? this.birthDate!.millisecondsSinceEpoch : this.birthDate!.toUtc().toIso8601String(); } else { - // json[r'birthDate'] = null; + json[r'birthDate'] = null; } if (this.color != null) { json[r'color'] = this.color; } else { - // json[r'color'] = null; + json[r'color'] = null; } json[r'createdAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') ? this.createdAt.millisecondsSinceEpoch @@ -105,7 +105,7 @@ class SyncPersonV1 { if (this.faceAssetId != null) { json[r'faceAssetId'] = this.faceAssetId; } else { - // json[r'faceAssetId'] = null; + json[r'faceAssetId'] = null; } json[r'id'] = this.id; json[r'isFavorite'] = this.isFavorite; diff --git a/mobile/openapi/lib/model/sync_request_type.dart b/mobile/openapi/lib/model/sync_request_type.dart index 030915b349..da08c4c597 100644 --- a/mobile/openapi/lib/model/sync_request_type.dart +++ b/mobile/openapi/lib/model/sync_request_type.dart @@ -28,8 +28,10 @@ class SyncRequestType { static const albumUsersV1 = SyncRequestType._(r'AlbumUsersV1'); static const albumToAssetsV1 = SyncRequestType._(r'AlbumToAssetsV1'); static const albumAssetsV1 = SyncRequestType._(r'AlbumAssetsV1'); + static const albumAssetsV2 = SyncRequestType._(r'AlbumAssetsV2'); static const albumAssetExifsV1 = SyncRequestType._(r'AlbumAssetExifsV1'); static const assetsV1 = SyncRequestType._(r'AssetsV1'); + static const assetsV2 = SyncRequestType._(r'AssetsV2'); static const assetExifsV1 = SyncRequestType._(r'AssetExifsV1'); static const assetEditsV1 = SyncRequestType._(r'AssetEditsV1'); static const assetMetadataV1 = SyncRequestType._(r'AssetMetadataV1'); @@ -39,6 +41,7 @@ class SyncRequestType { static const memoryToAssetsV1 = SyncRequestType._(r'MemoryToAssetsV1'); static const partnersV1 = SyncRequestType._(r'PartnersV1'); static const partnerAssetsV1 = SyncRequestType._(r'PartnerAssetsV1'); + static const partnerAssetsV2 = SyncRequestType._(r'PartnerAssetsV2'); static const partnerAssetExifsV1 = SyncRequestType._(r'PartnerAssetExifsV1'); static const partnerStacksV1 = SyncRequestType._(r'PartnerStacksV1'); static const stacksV1 = SyncRequestType._(r'StacksV1'); @@ -55,8 +58,10 @@ class SyncRequestType { albumUsersV1, albumToAssetsV1, albumAssetsV1, + albumAssetsV2, albumAssetExifsV1, assetsV1, + assetsV2, assetExifsV1, assetEditsV1, assetMetadataV1, @@ -66,6 +71,7 @@ class SyncRequestType { memoryToAssetsV1, partnersV1, partnerAssetsV1, + partnerAssetsV2, partnerAssetExifsV1, partnerStacksV1, stacksV1, @@ -117,8 +123,10 @@ class SyncRequestTypeTypeTransformer { case r'AlbumUsersV1': return SyncRequestType.albumUsersV1; case r'AlbumToAssetsV1': return SyncRequestType.albumToAssetsV1; case r'AlbumAssetsV1': return SyncRequestType.albumAssetsV1; + case r'AlbumAssetsV2': return SyncRequestType.albumAssetsV2; case r'AlbumAssetExifsV1': return SyncRequestType.albumAssetExifsV1; case r'AssetsV1': return SyncRequestType.assetsV1; + case r'AssetsV2': return SyncRequestType.assetsV2; case r'AssetExifsV1': return SyncRequestType.assetExifsV1; case r'AssetEditsV1': return SyncRequestType.assetEditsV1; case r'AssetMetadataV1': return SyncRequestType.assetMetadataV1; @@ -128,6 +136,7 @@ class SyncRequestTypeTypeTransformer { case r'MemoryToAssetsV1': return SyncRequestType.memoryToAssetsV1; case r'PartnersV1': return SyncRequestType.partnersV1; case r'PartnerAssetsV1': return SyncRequestType.partnerAssetsV1; + case r'PartnerAssetsV2': return SyncRequestType.partnerAssetsV2; case r'PartnerAssetExifsV1': return SyncRequestType.partnerAssetExifsV1; case r'PartnerStacksV1': return SyncRequestType.partnerStacksV1; case r'StacksV1': return SyncRequestType.stacksV1; diff --git a/mobile/openapi/lib/model/sync_stream_dto.dart b/mobile/openapi/lib/model/sync_stream_dto.dart index 932477cb15..12dcfb4b84 100644 --- a/mobile/openapi/lib/model/sync_stream_dto.dart +++ b/mobile/openapi/lib/model/sync_stream_dto.dart @@ -13,7 +13,7 @@ part of openapi.api; class SyncStreamDto { /// Returns a new [SyncStreamDto] instance. SyncStreamDto({ - this.reset, + this.reset = const Optional.absent(), this.types = const [], }); @@ -24,7 +24,7 @@ class SyncStreamDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? reset; + Optional reset; /// Sync request types List types; @@ -45,10 +45,9 @@ class SyncStreamDto { Map toJson() { final json = {}; - if (this.reset != null) { - json[r'reset'] = this.reset; - } else { - // json[r'reset'] = null; + if (this.reset.isPresent) { + final value = this.reset.value; + json[r'reset'] = value; } json[r'types'] = this.types; return json; @@ -63,7 +62,7 @@ class SyncStreamDto { final json = value.cast(); return SyncStreamDto( - reset: mapValueOfType(json, r'reset'), + reset: json.containsKey(r'reset') ? Optional.present(mapValueOfType(json, r'reset')) : const Optional.absent(), types: SyncRequestType.listFromJson(json[r'types']), ); } diff --git a/mobile/openapi/lib/model/sync_user_v1.dart b/mobile/openapi/lib/model/sync_user_v1.dart index 0a81593547..2fda2fbefa 100644 --- a/mobile/openapi/lib/model/sync_user_v1.dart +++ b/mobile/openapi/lib/model/sync_user_v1.dart @@ -13,7 +13,7 @@ part of openapi.api; class SyncUserV1 { /// Returns a new [SyncUserV1] instance. SyncUserV1({ - this.avatarColor, + this.avatarColor = const Optional.absent(), required this.deletedAt, required this.email, required this.hasProfileImage, @@ -22,7 +22,7 @@ class SyncUserV1 { required this.profileChangedAt, }); - UserAvatarColor? avatarColor; + Optional avatarColor; /// User deleted at DateTime? deletedAt; @@ -68,17 +68,16 @@ class SyncUserV1 { Map toJson() { final json = {}; - if (this.avatarColor != null) { - json[r'avatarColor'] = this.avatarColor; - } else { - // json[r'avatarColor'] = null; + if (this.avatarColor.isPresent) { + final value = this.avatarColor.value; + json[r'avatarColor'] = value; } if (this.deletedAt != null) { json[r'deletedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') ? this.deletedAt!.millisecondsSinceEpoch : this.deletedAt!.toUtc().toIso8601String(); } else { - // json[r'deletedAt'] = null; + json[r'deletedAt'] = null; } json[r'email'] = this.email; json[r'hasProfileImage'] = this.hasProfileImage; @@ -99,7 +98,7 @@ class SyncUserV1 { final json = value.cast(); return SyncUserV1( - avatarColor: UserAvatarColor.fromJson(json[r'avatarColor']), + avatarColor: json.containsKey(r'avatarColor') ? Optional.present(UserAvatarColor.fromJson(json[r'avatarColor'])) : const Optional.absent(), deletedAt: mapDateTime(json, r'deletedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), email: mapValueOfType(json, r'email')!, hasProfileImage: mapValueOfType(json, r'hasProfileImage')!, diff --git a/mobile/openapi/lib/model/system_config_f_fmpeg_dto.dart b/mobile/openapi/lib/model/system_config_f_fmpeg_dto.dart index ecf2e5da4a..79da8da97f 100644 --- a/mobile/openapi/lib/model/system_config_f_fmpeg_dto.dart +++ b/mobile/openapi/lib/model/system_config_f_fmpeg_dto.dart @@ -25,6 +25,7 @@ class SystemConfigFFmpegDto { required this.maxBitrate, required this.preferredHwDevice, required this.preset, + required this.realtime, required this.refs, required this.targetAudioCodec, required this.targetResolution, @@ -79,6 +80,8 @@ class SystemConfigFFmpegDto { /// Preset String preset; + SystemConfigFFmpegRealtimeDto realtime; + /// References /// /// Minimum value: 0 @@ -122,6 +125,7 @@ class SystemConfigFFmpegDto { other.maxBitrate == maxBitrate && other.preferredHwDevice == preferredHwDevice && other.preset == preset && + other.realtime == realtime && other.refs == refs && other.targetAudioCodec == targetAudioCodec && other.targetResolution == targetResolution && @@ -147,6 +151,7 @@ class SystemConfigFFmpegDto { (maxBitrate.hashCode) + (preferredHwDevice.hashCode) + (preset.hashCode) + + (realtime.hashCode) + (refs.hashCode) + (targetAudioCodec.hashCode) + (targetResolution.hashCode) + @@ -158,7 +163,7 @@ class SystemConfigFFmpegDto { (twoPass.hashCode); @override - String toString() => 'SystemConfigFFmpegDto[accel=$accel, accelDecode=$accelDecode, acceptedAudioCodecs=$acceptedAudioCodecs, acceptedContainers=$acceptedContainers, acceptedVideoCodecs=$acceptedVideoCodecs, bframes=$bframes, cqMode=$cqMode, crf=$crf, gopSize=$gopSize, maxBitrate=$maxBitrate, preferredHwDevice=$preferredHwDevice, preset=$preset, refs=$refs, targetAudioCodec=$targetAudioCodec, targetResolution=$targetResolution, targetVideoCodec=$targetVideoCodec, temporalAQ=$temporalAQ, threads=$threads, tonemap=$tonemap, transcode=$transcode, twoPass=$twoPass]'; + String toString() => 'SystemConfigFFmpegDto[accel=$accel, accelDecode=$accelDecode, acceptedAudioCodecs=$acceptedAudioCodecs, acceptedContainers=$acceptedContainers, acceptedVideoCodecs=$acceptedVideoCodecs, bframes=$bframes, cqMode=$cqMode, crf=$crf, gopSize=$gopSize, maxBitrate=$maxBitrate, preferredHwDevice=$preferredHwDevice, preset=$preset, realtime=$realtime, refs=$refs, targetAudioCodec=$targetAudioCodec, targetResolution=$targetResolution, targetVideoCodec=$targetVideoCodec, temporalAQ=$temporalAQ, threads=$threads, tonemap=$tonemap, transcode=$transcode, twoPass=$twoPass]'; Map toJson() { final json = {}; @@ -174,6 +179,7 @@ class SystemConfigFFmpegDto { json[r'maxBitrate'] = this.maxBitrate; json[r'preferredHwDevice'] = this.preferredHwDevice; json[r'preset'] = this.preset; + json[r'realtime'] = this.realtime; json[r'refs'] = this.refs; json[r'targetAudioCodec'] = this.targetAudioCodec; json[r'targetResolution'] = this.targetResolution; @@ -207,6 +213,7 @@ class SystemConfigFFmpegDto { maxBitrate: mapValueOfType(json, r'maxBitrate')!, preferredHwDevice: mapValueOfType(json, r'preferredHwDevice')!, preset: mapValueOfType(json, r'preset')!, + realtime: SystemConfigFFmpegRealtimeDto.fromJson(json[r'realtime'])!, refs: mapValueOfType(json, r'refs')!, targetAudioCodec: AudioCodec.fromJson(json[r'targetAudioCodec'])!, targetResolution: mapValueOfType(json, r'targetResolution')!, @@ -275,6 +282,7 @@ class SystemConfigFFmpegDto { 'maxBitrate', 'preferredHwDevice', 'preset', + 'realtime', 'refs', 'targetAudioCodec', 'targetResolution', diff --git a/mobile/openapi/lib/model/system_config_f_fmpeg_realtime_dto.dart b/mobile/openapi/lib/model/system_config_f_fmpeg_realtime_dto.dart new file mode 100644 index 0000000000..1a8669912e --- /dev/null +++ b/mobile/openapi/lib/model/system_config_f_fmpeg_realtime_dto.dart @@ -0,0 +1,100 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SystemConfigFFmpegRealtimeDto { + /// Returns a new [SystemConfigFFmpegRealtimeDto] instance. + SystemConfigFFmpegRealtimeDto({ + required this.enabled, + }); + + /// Enable real-time HLS transcoding (alpha) + bool enabled; + + @override + bool operator ==(Object other) => identical(this, other) || other is SystemConfigFFmpegRealtimeDto && + other.enabled == enabled; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (enabled.hashCode); + + @override + String toString() => 'SystemConfigFFmpegRealtimeDto[enabled=$enabled]'; + + Map toJson() { + final json = {}; + json[r'enabled'] = this.enabled; + return json; + } + + /// Returns a new [SystemConfigFFmpegRealtimeDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SystemConfigFFmpegRealtimeDto? fromJson(dynamic value) { + upgradeDto(value, "SystemConfigFFmpegRealtimeDto"); + if (value is Map) { + final json = value.cast(); + + return SystemConfigFFmpegRealtimeDto( + enabled: mapValueOfType(json, r'enabled')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SystemConfigFFmpegRealtimeDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SystemConfigFFmpegRealtimeDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SystemConfigFFmpegRealtimeDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SystemConfigFFmpegRealtimeDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'enabled', + }; +} + diff --git a/mobile/openapi/lib/model/system_config_generated_fullsize_image_dto.dart b/mobile/openapi/lib/model/system_config_generated_fullsize_image_dto.dart index d78f8fadd5..f0d27ffa85 100644 --- a/mobile/openapi/lib/model/system_config_generated_fullsize_image_dto.dart +++ b/mobile/openapi/lib/model/system_config_generated_fullsize_image_dto.dart @@ -15,7 +15,7 @@ class SystemConfigGeneratedFullsizeImageDto { SystemConfigGeneratedFullsizeImageDto({ required this.enabled, required this.format, - this.progressive, + this.progressive = const Optional.absent(), required this.quality, }); @@ -31,7 +31,7 @@ class SystemConfigGeneratedFullsizeImageDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? progressive; + Optional progressive; /// Quality /// @@ -61,10 +61,9 @@ class SystemConfigGeneratedFullsizeImageDto { final json = {}; json[r'enabled'] = this.enabled; json[r'format'] = this.format; - if (this.progressive != null) { - json[r'progressive'] = this.progressive; - } else { - // json[r'progressive'] = null; + if (this.progressive.isPresent) { + final value = this.progressive.value; + json[r'progressive'] = value; } json[r'quality'] = this.quality; return json; @@ -81,7 +80,7 @@ class SystemConfigGeneratedFullsizeImageDto { return SystemConfigGeneratedFullsizeImageDto( enabled: mapValueOfType(json, r'enabled')!, format: ImageFormat.fromJson(json[r'format'])!, - progressive: mapValueOfType(json, r'progressive'), + progressive: json.containsKey(r'progressive') ? Optional.present(mapValueOfType(json, r'progressive')) : const Optional.absent(), quality: mapValueOfType(json, r'quality')!, ); } diff --git a/mobile/openapi/lib/model/system_config_generated_image_dto.dart b/mobile/openapi/lib/model/system_config_generated_image_dto.dart index 2571c0cab0..6aff16322c 100644 --- a/mobile/openapi/lib/model/system_config_generated_image_dto.dart +++ b/mobile/openapi/lib/model/system_config_generated_image_dto.dart @@ -14,7 +14,7 @@ class SystemConfigGeneratedImageDto { /// Returns a new [SystemConfigGeneratedImageDto] instance. SystemConfigGeneratedImageDto({ required this.format, - this.progressive, + this.progressive = const Optional.absent(), required this.quality, required this.size, }); @@ -28,7 +28,7 @@ class SystemConfigGeneratedImageDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? progressive; + Optional progressive; /// Quality /// @@ -63,10 +63,9 @@ class SystemConfigGeneratedImageDto { Map toJson() { final json = {}; json[r'format'] = this.format; - if (this.progressive != null) { - json[r'progressive'] = this.progressive; - } else { - // json[r'progressive'] = null; + if (this.progressive.isPresent) { + final value = this.progressive.value; + json[r'progressive'] = value; } json[r'quality'] = this.quality; json[r'size'] = this.size; @@ -83,7 +82,7 @@ class SystemConfigGeneratedImageDto { return SystemConfigGeneratedImageDto( format: ImageFormat.fromJson(json[r'format'])!, - progressive: mapValueOfType(json, r'progressive'), + progressive: json.containsKey(r'progressive') ? Optional.present(mapValueOfType(json, r'progressive')) : const Optional.absent(), quality: mapValueOfType(json, r'quality')!, size: mapValueOfType(json, r'size')!, ); diff --git a/mobile/openapi/lib/model/system_config_new_version_check_dto.dart b/mobile/openapi/lib/model/system_config_new_version_check_dto.dart index ec2b400dfd..17ae9577e8 100644 --- a/mobile/openapi/lib/model/system_config_new_version_check_dto.dart +++ b/mobile/openapi/lib/model/system_config_new_version_check_dto.dart @@ -13,26 +13,32 @@ part of openapi.api; class SystemConfigNewVersionCheckDto { /// Returns a new [SystemConfigNewVersionCheckDto] instance. SystemConfigNewVersionCheckDto({ + required this.channel, required this.enabled, }); + ReleaseChannel channel; + /// Enabled bool enabled; @override bool operator ==(Object other) => identical(this, other) || other is SystemConfigNewVersionCheckDto && + other.channel == channel && other.enabled == enabled; @override int get hashCode => // ignore: unnecessary_parenthesis + (channel.hashCode) + (enabled.hashCode); @override - String toString() => 'SystemConfigNewVersionCheckDto[enabled=$enabled]'; + String toString() => 'SystemConfigNewVersionCheckDto[channel=$channel, enabled=$enabled]'; Map toJson() { final json = {}; + json[r'channel'] = this.channel; json[r'enabled'] = this.enabled; return json; } @@ -46,6 +52,7 @@ class SystemConfigNewVersionCheckDto { final json = value.cast(); return SystemConfigNewVersionCheckDto( + channel: ReleaseChannel.fromJson(json[r'channel'])!, enabled: mapValueOfType(json, r'enabled')!, ); } @@ -94,6 +101,7 @@ class SystemConfigNewVersionCheckDto { /// The list of required keys that must be present in a JSON. static const requiredKeys = { + 'channel', 'enabled', }; } diff --git a/mobile/openapi/lib/model/system_config_o_auth_dto.dart b/mobile/openapi/lib/model/system_config_o_auth_dto.dart index c65de03391..44eefe605c 100644 --- a/mobile/openapi/lib/model/system_config_o_auth_dto.dart +++ b/mobile/openapi/lib/model/system_config_o_auth_dto.dart @@ -167,7 +167,7 @@ class SystemConfigOAuthDto { if (this.defaultStorageQuota != null) { json[r'defaultStorageQuota'] = this.defaultStorageQuota; } else { - // json[r'defaultStorageQuota'] = null; + json[r'defaultStorageQuota'] = null; } json[r'enabled'] = this.enabled; json[r'endSessionEndpoint'] = this.endSessionEndpoint; diff --git a/mobile/openapi/lib/model/tag_create_dto.dart b/mobile/openapi/lib/model/tag_create_dto.dart index e05b29f1ed..e46f3fc8b6 100644 --- a/mobile/openapi/lib/model/tag_create_dto.dart +++ b/mobile/openapi/lib/model/tag_create_dto.dart @@ -13,19 +13,19 @@ part of openapi.api; class TagCreateDto { /// Returns a new [TagCreateDto] instance. TagCreateDto({ - this.color, + this.color = const Optional.absent(), required this.name, - this.parentId, + this.parentId = const Optional.absent(), }); /// Tag color (hex) - String? color; + Optional color; /// Tag name String name; /// Parent tag ID - String? parentId; + Optional parentId; @override bool operator ==(Object other) => identical(this, other) || other is TagCreateDto && @@ -45,16 +45,14 @@ class TagCreateDto { Map toJson() { final json = {}; - if (this.color != null) { - json[r'color'] = this.color; - } else { - // json[r'color'] = null; + if (this.color.isPresent) { + final value = this.color.value; + json[r'color'] = value; } json[r'name'] = this.name; - if (this.parentId != null) { - json[r'parentId'] = this.parentId; - } else { - // json[r'parentId'] = null; + if (this.parentId.isPresent) { + final value = this.parentId.value; + json[r'parentId'] = value; } return json; } @@ -68,9 +66,9 @@ class TagCreateDto { final json = value.cast(); return TagCreateDto( - color: mapValueOfType(json, r'color'), + color: json.containsKey(r'color') ? Optional.present(mapValueOfType(json, r'color')) : const Optional.absent(), name: mapValueOfType(json, r'name')!, - parentId: mapValueOfType(json, r'parentId'), + parentId: json.containsKey(r'parentId') ? Optional.present(mapValueOfType(json, r'parentId')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/tag_response_dto.dart b/mobile/openapi/lib/model/tag_response_dto.dart index 9a71912153..79a89f6d33 100644 --- a/mobile/openapi/lib/model/tag_response_dto.dart +++ b/mobile/openapi/lib/model/tag_response_dto.dart @@ -13,11 +13,11 @@ part of openapi.api; class TagResponseDto { /// Returns a new [TagResponseDto] instance. TagResponseDto({ - this.color, + this.color = const Optional.absent(), required this.createdAt, required this.id, required this.name, - this.parentId, + this.parentId = const Optional.absent(), required this.updatedAt, required this.value, }); @@ -29,7 +29,7 @@ class TagResponseDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? color; + Optional color; /// Creation date DateTime createdAt; @@ -47,7 +47,7 @@ class TagResponseDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? parentId; + Optional parentId; /// Last update date DateTime updatedAt; @@ -81,18 +81,16 @@ class TagResponseDto { Map toJson() { final json = {}; - if (this.color != null) { - json[r'color'] = this.color; - } else { - // json[r'color'] = null; + if (this.color.isPresent) { + final value = this.color.value; + json[r'color'] = value; } json[r'createdAt'] = this.createdAt.toUtc().toIso8601String(); json[r'id'] = this.id; json[r'name'] = this.name; - if (this.parentId != null) { - json[r'parentId'] = this.parentId; - } else { - // json[r'parentId'] = null; + if (this.parentId.isPresent) { + final value = this.parentId.value; + json[r'parentId'] = value; } json[r'updatedAt'] = this.updatedAt.toUtc().toIso8601String(); json[r'value'] = this.value; @@ -108,11 +106,11 @@ class TagResponseDto { final json = value.cast(); return TagResponseDto( - color: mapValueOfType(json, r'color'), + color: json.containsKey(r'color') ? Optional.present(mapValueOfType(json, r'color')) : const Optional.absent(), createdAt: mapDateTime(json, r'createdAt', r'')!, id: mapValueOfType(json, r'id')!, name: mapValueOfType(json, r'name')!, - parentId: mapValueOfType(json, r'parentId'), + parentId: json.containsKey(r'parentId') ? Optional.present(mapValueOfType(json, r'parentId')) : const Optional.absent(), updatedAt: mapDateTime(json, r'updatedAt', r'')!, value: mapValueOfType(json, r'value')!, ); diff --git a/mobile/openapi/lib/model/tag_update_dto.dart b/mobile/openapi/lib/model/tag_update_dto.dart index 98cb6af523..d66bb9097e 100644 --- a/mobile/openapi/lib/model/tag_update_dto.dart +++ b/mobile/openapi/lib/model/tag_update_dto.dart @@ -13,11 +13,11 @@ part of openapi.api; class TagUpdateDto { /// Returns a new [TagUpdateDto] instance. TagUpdateDto({ - this.color, + this.color = const Optional.absent(), }); /// Tag color (hex) - String? color; + Optional color; @override bool operator ==(Object other) => identical(this, other) || other is TagUpdateDto && @@ -33,10 +33,9 @@ class TagUpdateDto { Map toJson() { final json = {}; - if (this.color != null) { - json[r'color'] = this.color; - } else { - // json[r'color'] = null; + if (this.color.isPresent) { + final value = this.color.value; + json[r'color'] = value; } return json; } @@ -50,7 +49,7 @@ class TagUpdateDto { final json = value.cast(); return TagUpdateDto( - color: mapValueOfType(json, r'color'), + color: json.containsKey(r'color') ? Optional.present(mapValueOfType(json, r'color')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/tags_update.dart b/mobile/openapi/lib/model/tags_update.dart index e42357e3d4..9a9e78f1d3 100644 --- a/mobile/openapi/lib/model/tags_update.dart +++ b/mobile/openapi/lib/model/tags_update.dart @@ -13,8 +13,8 @@ part of openapi.api; class TagsUpdate { /// Returns a new [TagsUpdate] instance. TagsUpdate({ - this.enabled, - this.sidebarWeb, + this.enabled = const Optional.absent(), + this.sidebarWeb = const Optional.absent(), }); /// Whether tags are enabled @@ -24,7 +24,7 @@ class TagsUpdate { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? enabled; + Optional enabled; /// Whether tags appear in web sidebar /// @@ -33,7 +33,7 @@ class TagsUpdate { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? sidebarWeb; + Optional sidebarWeb; @override bool operator ==(Object other) => identical(this, other) || other is TagsUpdate && @@ -51,15 +51,13 @@ class TagsUpdate { Map toJson() { final json = {}; - if (this.enabled != null) { - json[r'enabled'] = this.enabled; - } else { - // json[r'enabled'] = null; + if (this.enabled.isPresent) { + final value = this.enabled.value; + json[r'enabled'] = value; } - if (this.sidebarWeb != null) { - json[r'sidebarWeb'] = this.sidebarWeb; - } else { - // json[r'sidebarWeb'] = null; + if (this.sidebarWeb.isPresent) { + final value = this.sidebarWeb.value; + json[r'sidebarWeb'] = value; } return json; } @@ -73,8 +71,8 @@ class TagsUpdate { final json = value.cast(); return TagsUpdate( - enabled: mapValueOfType(json, r'enabled'), - sidebarWeb: mapValueOfType(json, r'sidebarWeb'), + enabled: json.containsKey(r'enabled') ? Optional.present(mapValueOfType(json, r'enabled')) : const Optional.absent(), + sidebarWeb: json.containsKey(r'sidebarWeb') ? Optional.present(mapValueOfType(json, r'sidebarWeb')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/time_bucket_asset_response_dto.dart b/mobile/openapi/lib/model/time_bucket_asset_response_dto.dart index 08e690de71..154ca97504 100644 --- a/mobile/openapi/lib/model/time_bucket_asset_response_dto.dart +++ b/mobile/openapi/lib/model/time_bucket_asset_response_dto.dart @@ -13,34 +13,38 @@ part of openapi.api; class TimeBucketAssetResponseDto { /// Returns a new [TimeBucketAssetResponseDto] instance. TimeBucketAssetResponseDto({ - this.city = const [], - this.country = const [], + this.city = const Optional.present(const []), + this.country = const Optional.present(const []), + this.createdAt = const [], this.duration = const [], this.fileCreatedAt = const [], this.id = const [], this.isFavorite = const [], this.isImage = const [], this.isTrashed = const [], - this.latitude = const [], + this.latitude = const Optional.present(const []), this.livePhotoVideoId = const [], this.localOffsetHours = const [], - this.longitude = const [], + this.longitude = const Optional.present(const []), this.ownerId = const [], this.projectionType = const [], this.ratio = const [], - this.stack = const [], + this.stack = const Optional.present(const []), this.thumbhash = const [], this.visibility = const [], }); /// Array of city names extracted from EXIF GPS data - List city; + Optional?> city; /// Array of country names extracted from EXIF GPS data - List country; + Optional?> country; + + /// Array of UTC timestamps when each asset was originally uploaded to Immich + List createdAt; /// Array of video/gif durations in milliseconds (null for static images) - List duration; + List duration; /// Array of file creation timestamps in UTC List fileCreatedAt; @@ -58,31 +62,31 @@ class TimeBucketAssetResponseDto { List isTrashed; /// Array of latitude coordinates extracted from EXIF GPS data - List latitude; + Optional?> latitude; /// Array of live photo video asset IDs (null for non-live photos) - List livePhotoVideoId; + List livePhotoVideoId; /// Array of UTC offset hours at the time each photo was taken. Positive values are east of UTC, negative values are west of UTC. Values may be fractional (e.g., 5.5 for +05:30, -9.75 for -09:45). Applying this offset to 'fileCreatedAt' will give you the time the photo was taken from the photographer's perspective. List localOffsetHours; /// Array of longitude coordinates extracted from EXIF GPS data - List longitude; + Optional?> longitude; /// Array of owner IDs for each asset List ownerId; /// Array of projection types for 360ยฐ content (e.g., \"EQUIRECTANGULAR\", \"CUBEFACE\", \"CYLINDRICAL\") - List projectionType; + List projectionType; /// Array of aspect ratios (width/height) for each asset List ratio; /// Array of stack information as [stackId, assetCount] tuples (null for non-stacked assets) - List?> stack; + Optional?>?> stack; /// Array of BlurHash strings for generating asset previews (base64 encoded) - List thumbhash; + List thumbhash; /// Array of visibility statuses for each asset (e.g., ARCHIVE, TIMELINE, HIDDEN, LOCKED) List visibility; @@ -91,6 +95,7 @@ class TimeBucketAssetResponseDto { bool operator ==(Object other) => identical(this, other) || other is TimeBucketAssetResponseDto && _deepEquality.equals(other.city, city) && _deepEquality.equals(other.country, country) && + _deepEquality.equals(other.createdAt, createdAt) && _deepEquality.equals(other.duration, duration) && _deepEquality.equals(other.fileCreatedAt, fileCreatedAt) && _deepEquality.equals(other.id, id) && @@ -113,6 +118,7 @@ class TimeBucketAssetResponseDto { // ignore: unnecessary_parenthesis (city.hashCode) + (country.hashCode) + + (createdAt.hashCode) + (duration.hashCode) + (fileCreatedAt.hashCode) + (id.hashCode) + @@ -131,26 +137,42 @@ class TimeBucketAssetResponseDto { (visibility.hashCode); @override - String toString() => 'TimeBucketAssetResponseDto[city=$city, country=$country, duration=$duration, fileCreatedAt=$fileCreatedAt, id=$id, isFavorite=$isFavorite, isImage=$isImage, isTrashed=$isTrashed, latitude=$latitude, livePhotoVideoId=$livePhotoVideoId, localOffsetHours=$localOffsetHours, longitude=$longitude, ownerId=$ownerId, projectionType=$projectionType, ratio=$ratio, stack=$stack, thumbhash=$thumbhash, visibility=$visibility]'; + String toString() => 'TimeBucketAssetResponseDto[city=$city, country=$country, createdAt=$createdAt, duration=$duration, fileCreatedAt=$fileCreatedAt, id=$id, isFavorite=$isFavorite, isImage=$isImage, isTrashed=$isTrashed, latitude=$latitude, livePhotoVideoId=$livePhotoVideoId, localOffsetHours=$localOffsetHours, longitude=$longitude, ownerId=$ownerId, projectionType=$projectionType, ratio=$ratio, stack=$stack, thumbhash=$thumbhash, visibility=$visibility]'; Map toJson() { final json = {}; - json[r'city'] = this.city; - json[r'country'] = this.country; + if (this.city.isPresent) { + final value = this.city.value; + json[r'city'] = value; + } + if (this.country.isPresent) { + final value = this.country.value; + json[r'country'] = value; + } + json[r'createdAt'] = this.createdAt; json[r'duration'] = this.duration; json[r'fileCreatedAt'] = this.fileCreatedAt; json[r'id'] = this.id; json[r'isFavorite'] = this.isFavorite; json[r'isImage'] = this.isImage; json[r'isTrashed'] = this.isTrashed; - json[r'latitude'] = this.latitude; + if (this.latitude.isPresent) { + final value = this.latitude.value; + json[r'latitude'] = value; + } json[r'livePhotoVideoId'] = this.livePhotoVideoId; json[r'localOffsetHours'] = this.localOffsetHours; - json[r'longitude'] = this.longitude; + if (this.longitude.isPresent) { + final value = this.longitude.value; + json[r'longitude'] = value; + } json[r'ownerId'] = this.ownerId; json[r'projectionType'] = this.projectionType; json[r'ratio'] = this.ratio; - json[r'stack'] = this.stack; + if (this.stack.isPresent) { + final value = this.stack.value; + json[r'stack'] = value; + } json[r'thumbhash'] = this.thumbhash; json[r'visibility'] = this.visibility; return json; @@ -165,11 +187,14 @@ class TimeBucketAssetResponseDto { final json = value.cast(); return TimeBucketAssetResponseDto( - city: json[r'city'] is Iterable + city: json.containsKey(r'city') ? Optional.present(json[r'city'] is Iterable ? (json[r'city'] as Iterable).cast().toList(growable: false) - : const [], - country: json[r'country'] is Iterable + : const []) : const Optional.absent(), + country: json.containsKey(r'country') ? Optional.present(json[r'country'] is Iterable ? (json[r'country'] as Iterable).cast().toList(growable: false) + : const []) : const Optional.absent(), + createdAt: json[r'createdAt'] is Iterable + ? (json[r'createdAt'] as Iterable).cast().toList(growable: false) : const [], duration: json[r'duration'] is Iterable ? (json[r'duration'] as Iterable).cast().toList(growable: false) @@ -189,18 +214,18 @@ class TimeBucketAssetResponseDto { isTrashed: json[r'isTrashed'] is Iterable ? (json[r'isTrashed'] as Iterable).cast().toList(growable: false) : const [], - latitude: json[r'latitude'] is Iterable + latitude: json.containsKey(r'latitude') ? Optional.present(json[r'latitude'] is Iterable ? (json[r'latitude'] as Iterable).cast().toList(growable: false) - : const [], + : const []) : const Optional.absent(), livePhotoVideoId: json[r'livePhotoVideoId'] is Iterable ? (json[r'livePhotoVideoId'] as Iterable).cast().toList(growable: false) : const [], localOffsetHours: json[r'localOffsetHours'] is Iterable ? (json[r'localOffsetHours'] as Iterable).cast().toList(growable: false) : const [], - longitude: json[r'longitude'] is Iterable + longitude: json.containsKey(r'longitude') ? Optional.present(json[r'longitude'] is Iterable ? (json[r'longitude'] as Iterable).cast().toList(growable: false) - : const [], + : const []) : const Optional.absent(), ownerId: json[r'ownerId'] is Iterable ? (json[r'ownerId'] as Iterable).cast().toList(growable: false) : const [], @@ -210,11 +235,11 @@ class TimeBucketAssetResponseDto { ratio: json[r'ratio'] is Iterable ? (json[r'ratio'] as Iterable).cast().toList(growable: false) : const [], - stack: json[r'stack'] is List + stack: json.containsKey(r'stack') ? Optional.present(json[r'stack'] is List ? (json[r'stack'] as List).map((e) => - e == null ? null : (e as List).cast() + e == null ? null : (e as List).map((value) => value as String).toList(growable: false) ).toList() - : const [], + : const []) : const Optional.absent(), thumbhash: json[r'thumbhash'] is Iterable ? (json[r'thumbhash'] as Iterable).cast().toList(growable: false) : const [], @@ -266,8 +291,7 @@ class TimeBucketAssetResponseDto { /// The list of required keys that must be present in a JSON. static const requiredKeys = { - 'city', - 'country', + 'createdAt', 'duration', 'fileCreatedAt', 'id', diff --git a/mobile/openapi/lib/model/update_album_dto.dart b/mobile/openapi/lib/model/update_album_dto.dart index ae4a5c1f87..8995a69656 100644 --- a/mobile/openapi/lib/model/update_album_dto.dart +++ b/mobile/openapi/lib/model/update_album_dto.dart @@ -13,11 +13,11 @@ part of openapi.api; class UpdateAlbumDto { /// Returns a new [UpdateAlbumDto] instance. UpdateAlbumDto({ - this.albumName, - this.albumThumbnailAssetId, - this.description, - this.isActivityEnabled, - this.order, + this.albumName = const Optional.absent(), + this.albumThumbnailAssetId = const Optional.absent(), + this.description = const Optional.absent(), + this.isActivityEnabled = const Optional.absent(), + this.order = const Optional.absent(), }); /// Album name @@ -27,7 +27,7 @@ class UpdateAlbumDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? albumName; + Optional albumName; /// Album thumbnail asset ID /// @@ -36,7 +36,7 @@ class UpdateAlbumDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? albumThumbnailAssetId; + Optional albumThumbnailAssetId; /// Album description /// @@ -45,7 +45,7 @@ class UpdateAlbumDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? description; + Optional description; /// Enable activity feed /// @@ -54,7 +54,7 @@ class UpdateAlbumDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? isActivityEnabled; + Optional isActivityEnabled; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -62,7 +62,7 @@ class UpdateAlbumDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - AssetOrder? order; + Optional order; @override bool operator ==(Object other) => identical(this, other) || other is UpdateAlbumDto && @@ -86,30 +86,25 @@ class UpdateAlbumDto { Map toJson() { final json = {}; - if (this.albumName != null) { - json[r'albumName'] = this.albumName; - } else { - // json[r'albumName'] = null; + if (this.albumName.isPresent) { + final value = this.albumName.value; + json[r'albumName'] = value; } - if (this.albumThumbnailAssetId != null) { - json[r'albumThumbnailAssetId'] = this.albumThumbnailAssetId; - } else { - // json[r'albumThumbnailAssetId'] = null; + if (this.albumThumbnailAssetId.isPresent) { + final value = this.albumThumbnailAssetId.value; + json[r'albumThumbnailAssetId'] = value; } - if (this.description != null) { - json[r'description'] = this.description; - } else { - // json[r'description'] = null; + if (this.description.isPresent) { + final value = this.description.value; + json[r'description'] = value; } - if (this.isActivityEnabled != null) { - json[r'isActivityEnabled'] = this.isActivityEnabled; - } else { - // json[r'isActivityEnabled'] = null; + if (this.isActivityEnabled.isPresent) { + final value = this.isActivityEnabled.value; + json[r'isActivityEnabled'] = value; } - if (this.order != null) { - json[r'order'] = this.order; - } else { - // json[r'order'] = null; + if (this.order.isPresent) { + final value = this.order.value; + json[r'order'] = value; } return json; } @@ -123,11 +118,11 @@ class UpdateAlbumDto { final json = value.cast(); return UpdateAlbumDto( - albumName: mapValueOfType(json, r'albumName'), - albumThumbnailAssetId: mapValueOfType(json, r'albumThumbnailAssetId'), - description: mapValueOfType(json, r'description'), - isActivityEnabled: mapValueOfType(json, r'isActivityEnabled'), - order: AssetOrder.fromJson(json[r'order']), + albumName: json.containsKey(r'albumName') ? Optional.present(mapValueOfType(json, r'albumName')) : const Optional.absent(), + albumThumbnailAssetId: json.containsKey(r'albumThumbnailAssetId') ? Optional.present(mapValueOfType(json, r'albumThumbnailAssetId')) : const Optional.absent(), + description: json.containsKey(r'description') ? Optional.present(mapValueOfType(json, r'description')) : const Optional.absent(), + isActivityEnabled: json.containsKey(r'isActivityEnabled') ? Optional.present(mapValueOfType(json, r'isActivityEnabled')) : const Optional.absent(), + order: json.containsKey(r'order') ? Optional.present(AssetOrder.fromJson(json[r'order'])) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/update_asset_dto.dart b/mobile/openapi/lib/model/update_asset_dto.dart index 2c4c3352ea..bd64124673 100644 --- a/mobile/openapi/lib/model/update_asset_dto.dart +++ b/mobile/openapi/lib/model/update_asset_dto.dart @@ -13,14 +13,14 @@ part of openapi.api; class UpdateAssetDto { /// Returns a new [UpdateAssetDto] instance. UpdateAssetDto({ - this.dateTimeOriginal, - this.description, - this.isFavorite, - this.latitude, - this.livePhotoVideoId, - this.longitude, - this.rating, - this.visibility, + this.dateTimeOriginal = const Optional.absent(), + this.description = const Optional.absent(), + this.isFavorite = const Optional.absent(), + this.latitude = const Optional.absent(), + this.livePhotoVideoId = const Optional.absent(), + this.longitude = const Optional.absent(), + this.rating = const Optional.absent(), + this.visibility = const Optional.absent(), }); /// Original date and time @@ -30,7 +30,7 @@ class UpdateAssetDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? dateTimeOriginal; + Optional dateTimeOriginal; /// Asset description /// @@ -39,7 +39,7 @@ class UpdateAssetDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? description; + Optional description; /// Mark as favorite /// @@ -48,7 +48,7 @@ class UpdateAssetDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? isFavorite; + Optional isFavorite; /// Latitude coordinate /// @@ -60,10 +60,10 @@ class UpdateAssetDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - num? latitude; + Optional latitude; /// Live photo video ID - String? livePhotoVideoId; + Optional livePhotoVideoId; /// Longitude coordinate /// @@ -75,13 +75,13 @@ class UpdateAssetDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - num? longitude; + Optional longitude; /// Rating in range [1-5], or null for unrated /// /// Minimum value: -1 /// Maximum value: 5 - int? rating; + Optional rating; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -89,7 +89,7 @@ class UpdateAssetDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - AssetVisibility? visibility; + Optional visibility; @override bool operator ==(Object other) => identical(this, other) || other is UpdateAssetDto && @@ -119,45 +119,37 @@ class UpdateAssetDto { Map toJson() { final json = {}; - if (this.dateTimeOriginal != null) { - json[r'dateTimeOriginal'] = this.dateTimeOriginal; - } else { - // json[r'dateTimeOriginal'] = null; + if (this.dateTimeOriginal.isPresent) { + final value = this.dateTimeOriginal.value; + json[r'dateTimeOriginal'] = value; } - if (this.description != null) { - json[r'description'] = this.description; - } else { - // json[r'description'] = null; + if (this.description.isPresent) { + final value = this.description.value; + json[r'description'] = value; } - if (this.isFavorite != null) { - json[r'isFavorite'] = this.isFavorite; - } else { - // json[r'isFavorite'] = null; + if (this.isFavorite.isPresent) { + final value = this.isFavorite.value; + json[r'isFavorite'] = value; } - if (this.latitude != null) { - json[r'latitude'] = this.latitude; - } else { - // json[r'latitude'] = null; + if (this.latitude.isPresent) { + final value = this.latitude.value; + json[r'latitude'] = value; } - if (this.livePhotoVideoId != null) { - json[r'livePhotoVideoId'] = this.livePhotoVideoId; - } else { - // json[r'livePhotoVideoId'] = null; + if (this.livePhotoVideoId.isPresent) { + final value = this.livePhotoVideoId.value; + json[r'livePhotoVideoId'] = value; } - if (this.longitude != null) { - json[r'longitude'] = this.longitude; - } else { - // json[r'longitude'] = null; + if (this.longitude.isPresent) { + final value = this.longitude.value; + json[r'longitude'] = value; } - if (this.rating != null) { - json[r'rating'] = this.rating; - } else { - // json[r'rating'] = null; + if (this.rating.isPresent) { + final value = this.rating.value; + json[r'rating'] = value; } - if (this.visibility != null) { - json[r'visibility'] = this.visibility; - } else { - // json[r'visibility'] = null; + if (this.visibility.isPresent) { + final value = this.visibility.value; + json[r'visibility'] = value; } return json; } @@ -171,14 +163,14 @@ class UpdateAssetDto { final json = value.cast(); return UpdateAssetDto( - dateTimeOriginal: mapValueOfType(json, r'dateTimeOriginal'), - description: mapValueOfType(json, r'description'), - isFavorite: mapValueOfType(json, r'isFavorite'), - latitude: num.parse('${json[r'latitude']}'), - livePhotoVideoId: mapValueOfType(json, r'livePhotoVideoId'), - longitude: num.parse('${json[r'longitude']}'), - rating: mapValueOfType(json, r'rating'), - visibility: AssetVisibility.fromJson(json[r'visibility']), + dateTimeOriginal: json.containsKey(r'dateTimeOriginal') ? Optional.present(mapValueOfType(json, r'dateTimeOriginal')) : const Optional.absent(), + description: json.containsKey(r'description') ? Optional.present(mapValueOfType(json, r'description')) : const Optional.absent(), + isFavorite: json.containsKey(r'isFavorite') ? Optional.present(mapValueOfType(json, r'isFavorite')) : const Optional.absent(), + latitude: json.containsKey(r'latitude') ? Optional.present(json[r'latitude'] == null ? null : num.parse('${json[r'latitude']}')) : const Optional.absent(), + livePhotoVideoId: json.containsKey(r'livePhotoVideoId') ? Optional.present(mapValueOfType(json, r'livePhotoVideoId')) : const Optional.absent(), + longitude: json.containsKey(r'longitude') ? Optional.present(json[r'longitude'] == null ? null : num.parse('${json[r'longitude']}')) : const Optional.absent(), + rating: json.containsKey(r'rating') ? Optional.present(json[r'rating'] == null ? null : int.parse('${json[r'rating']}')) : const Optional.absent(), + visibility: json.containsKey(r'visibility') ? Optional.present(AssetVisibility.fromJson(json[r'visibility'])) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/update_library_dto.dart b/mobile/openapi/lib/model/update_library_dto.dart index 276d43ecd9..44aa042f35 100644 --- a/mobile/openapi/lib/model/update_library_dto.dart +++ b/mobile/openapi/lib/model/update_library_dto.dart @@ -13,16 +13,16 @@ part of openapi.api; class UpdateLibraryDto { /// Returns a new [UpdateLibraryDto] instance. UpdateLibraryDto({ - this.exclusionPatterns = const [], - this.importPaths = const [], - this.name, + this.exclusionPatterns = const Optional.present(const []), + this.importPaths = const Optional.present(const []), + this.name = const Optional.absent(), }); /// Exclusion patterns (max 128) - List exclusionPatterns; + Optional?> exclusionPatterns; /// Import paths (max 128) - List importPaths; + Optional?> importPaths; /// Library name /// @@ -31,7 +31,7 @@ class UpdateLibraryDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? name; + Optional name; @override bool operator ==(Object other) => identical(this, other) || other is UpdateLibraryDto && @@ -51,12 +51,17 @@ class UpdateLibraryDto { Map toJson() { final json = {}; - json[r'exclusionPatterns'] = this.exclusionPatterns; - json[r'importPaths'] = this.importPaths; - if (this.name != null) { - json[r'name'] = this.name; - } else { - // json[r'name'] = null; + if (this.exclusionPatterns.isPresent) { + final value = this.exclusionPatterns.value; + json[r'exclusionPatterns'] = value; + } + if (this.importPaths.isPresent) { + final value = this.importPaths.value; + json[r'importPaths'] = value; + } + if (this.name.isPresent) { + final value = this.name.value; + json[r'name'] = value; } return json; } @@ -70,13 +75,13 @@ class UpdateLibraryDto { final json = value.cast(); return UpdateLibraryDto( - exclusionPatterns: json[r'exclusionPatterns'] is Iterable + exclusionPatterns: json.containsKey(r'exclusionPatterns') ? Optional.present(json[r'exclusionPatterns'] is Iterable ? (json[r'exclusionPatterns'] as Iterable).cast().toList(growable: false) - : const [], - importPaths: json[r'importPaths'] is Iterable + : const []) : const Optional.absent(), + importPaths: json.containsKey(r'importPaths') ? Optional.present(json[r'importPaths'] is Iterable ? (json[r'importPaths'] as Iterable).cast().toList(growable: false) - : const [], - name: mapValueOfType(json, r'name'), + : const []) : const Optional.absent(), + name: json.containsKey(r'name') ? Optional.present(mapValueOfType(json, r'name')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/usage_by_user_dto.dart b/mobile/openapi/lib/model/usage_by_user_dto.dart index 462b82c3e0..fbf2cc02e4 100644 --- a/mobile/openapi/lib/model/usage_by_user_dto.dart +++ b/mobile/openapi/lib/model/usage_by_user_dto.dart @@ -97,7 +97,7 @@ class UsageByUserDto { if (this.quotaSizeInBytes != null) { json[r'quotaSizeInBytes'] = this.quotaSizeInBytes; } else { - // json[r'quotaSizeInBytes'] = null; + json[r'quotaSizeInBytes'] = null; } json[r'usage'] = this.usage; json[r'usagePhotos'] = this.usagePhotos; diff --git a/mobile/openapi/lib/model/user_admin_create_dto.dart b/mobile/openapi/lib/model/user_admin_create_dto.dart index 54da0b0566..8ed867c2cf 100644 --- a/mobile/openapi/lib/model/user_admin_create_dto.dart +++ b/mobile/openapi/lib/model/user_admin_create_dto.dart @@ -13,19 +13,19 @@ part of openapi.api; class UserAdminCreateDto { /// Returns a new [UserAdminCreateDto] instance. UserAdminCreateDto({ - this.avatarColor, + this.avatarColor = const Optional.absent(), required this.email, - this.isAdmin, + this.isAdmin = const Optional.absent(), required this.name, - this.notify, + this.notify = const Optional.absent(), required this.password, - this.pinCode, - this.quotaSizeInBytes, - this.shouldChangePassword, - this.storageLabel, + this.pinCode = const Optional.absent(), + this.quotaSizeInBytes = const Optional.absent(), + this.shouldChangePassword = const Optional.absent(), + this.storageLabel = const Optional.absent(), }); - UserAvatarColor? avatarColor; + Optional avatarColor; /// User email String email; @@ -37,7 +37,7 @@ class UserAdminCreateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? isAdmin; + Optional isAdmin; /// User name String name; @@ -49,19 +49,19 @@ class UserAdminCreateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? notify; + Optional notify; /// User password String password; /// PIN code - String? pinCode; + Optional pinCode; /// Storage quota in bytes /// /// Minimum value: 0 /// Maximum value: 9007199254740991 - int? quotaSizeInBytes; + Optional quotaSizeInBytes; /// Require password change on next login /// @@ -70,10 +70,10 @@ class UserAdminCreateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? shouldChangePassword; + Optional shouldChangePassword; /// Storage label - String? storageLabel; + Optional storageLabel; @override bool operator ==(Object other) => identical(this, other) || other is UserAdminCreateDto && @@ -107,43 +107,36 @@ class UserAdminCreateDto { Map toJson() { final json = {}; - if (this.avatarColor != null) { - json[r'avatarColor'] = this.avatarColor; - } else { - // json[r'avatarColor'] = null; + if (this.avatarColor.isPresent) { + final value = this.avatarColor.value; + json[r'avatarColor'] = value; } json[r'email'] = this.email; - if (this.isAdmin != null) { - json[r'isAdmin'] = this.isAdmin; - } else { - // json[r'isAdmin'] = null; + if (this.isAdmin.isPresent) { + final value = this.isAdmin.value; + json[r'isAdmin'] = value; } json[r'name'] = this.name; - if (this.notify != null) { - json[r'notify'] = this.notify; - } else { - // json[r'notify'] = null; + if (this.notify.isPresent) { + final value = this.notify.value; + json[r'notify'] = value; } json[r'password'] = this.password; - if (this.pinCode != null) { - json[r'pinCode'] = this.pinCode; - } else { - // json[r'pinCode'] = null; + if (this.pinCode.isPresent) { + final value = this.pinCode.value; + json[r'pinCode'] = value; } - if (this.quotaSizeInBytes != null) { - json[r'quotaSizeInBytes'] = this.quotaSizeInBytes; - } else { - // json[r'quotaSizeInBytes'] = null; + if (this.quotaSizeInBytes.isPresent) { + final value = this.quotaSizeInBytes.value; + json[r'quotaSizeInBytes'] = value; } - if (this.shouldChangePassword != null) { - json[r'shouldChangePassword'] = this.shouldChangePassword; - } else { - // json[r'shouldChangePassword'] = null; + if (this.shouldChangePassword.isPresent) { + final value = this.shouldChangePassword.value; + json[r'shouldChangePassword'] = value; } - if (this.storageLabel != null) { - json[r'storageLabel'] = this.storageLabel; - } else { - // json[r'storageLabel'] = null; + if (this.storageLabel.isPresent) { + final value = this.storageLabel.value; + json[r'storageLabel'] = value; } return json; } @@ -157,16 +150,16 @@ class UserAdminCreateDto { final json = value.cast(); return UserAdminCreateDto( - avatarColor: UserAvatarColor.fromJson(json[r'avatarColor']), + avatarColor: json.containsKey(r'avatarColor') ? Optional.present(UserAvatarColor.fromJson(json[r'avatarColor'])) : const Optional.absent(), email: mapValueOfType(json, r'email')!, - isAdmin: mapValueOfType(json, r'isAdmin'), + isAdmin: json.containsKey(r'isAdmin') ? Optional.present(mapValueOfType(json, r'isAdmin')) : const Optional.absent(), name: mapValueOfType(json, r'name')!, - notify: mapValueOfType(json, r'notify'), + notify: json.containsKey(r'notify') ? Optional.present(mapValueOfType(json, r'notify')) : const Optional.absent(), password: mapValueOfType(json, r'password')!, - pinCode: mapValueOfType(json, r'pinCode'), - quotaSizeInBytes: mapValueOfType(json, r'quotaSizeInBytes'), - shouldChangePassword: mapValueOfType(json, r'shouldChangePassword'), - storageLabel: mapValueOfType(json, r'storageLabel'), + pinCode: json.containsKey(r'pinCode') ? Optional.present(mapValueOfType(json, r'pinCode')) : const Optional.absent(), + quotaSizeInBytes: json.containsKey(r'quotaSizeInBytes') ? Optional.present(json[r'quotaSizeInBytes'] == null ? null : int.parse('${json[r'quotaSizeInBytes']}')) : const Optional.absent(), + shouldChangePassword: json.containsKey(r'shouldChangePassword') ? Optional.present(mapValueOfType(json, r'shouldChangePassword')) : const Optional.absent(), + storageLabel: json.containsKey(r'storageLabel') ? Optional.present(mapValueOfType(json, r'storageLabel')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/user_admin_delete_dto.dart b/mobile/openapi/lib/model/user_admin_delete_dto.dart index 6be70f37b7..8d7ab73076 100644 --- a/mobile/openapi/lib/model/user_admin_delete_dto.dart +++ b/mobile/openapi/lib/model/user_admin_delete_dto.dart @@ -13,7 +13,7 @@ part of openapi.api; class UserAdminDeleteDto { /// Returns a new [UserAdminDeleteDto] instance. UserAdminDeleteDto({ - this.force, + this.force = const Optional.absent(), }); /// Force delete even if user has assets @@ -23,7 +23,7 @@ class UserAdminDeleteDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? force; + Optional force; @override bool operator ==(Object other) => identical(this, other) || other is UserAdminDeleteDto && @@ -39,10 +39,9 @@ class UserAdminDeleteDto { Map toJson() { final json = {}; - if (this.force != null) { - json[r'force'] = this.force; - } else { - // json[r'force'] = null; + if (this.force.isPresent) { + final value = this.force.value; + json[r'force'] = value; } return json; } @@ -56,7 +55,7 @@ class UserAdminDeleteDto { final json = value.cast(); return UserAdminDeleteDto( - force: mapValueOfType(json, r'force'), + force: json.containsKey(r'force') ? Optional.present(mapValueOfType(json, r'force')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/user_admin_response_dto.dart b/mobile/openapi/lib/model/user_admin_response_dto.dart index 09f8cedce4..733f217b68 100644 --- a/mobile/openapi/lib/model/user_admin_response_dto.dart +++ b/mobile/openapi/lib/model/user_admin_response_dto.dart @@ -141,7 +141,7 @@ class UserAdminResponseDto { ? this.deletedAt!.millisecondsSinceEpoch : this.deletedAt!.toUtc().toIso8601String(); } else { - // json[r'deletedAt'] = null; + json[r'deletedAt'] = null; } json[r'email'] = this.email; json[r'id'] = this.id; @@ -149,7 +149,7 @@ class UserAdminResponseDto { if (this.license != null) { json[r'license'] = this.license; } else { - // json[r'license'] = null; + json[r'license'] = null; } json[r'name'] = this.name; json[r'oauthId'] = this.oauthId; @@ -158,19 +158,19 @@ class UserAdminResponseDto { if (this.quotaSizeInBytes != null) { json[r'quotaSizeInBytes'] = this.quotaSizeInBytes; } else { - // json[r'quotaSizeInBytes'] = null; + json[r'quotaSizeInBytes'] = null; } if (this.quotaUsageInBytes != null) { json[r'quotaUsageInBytes'] = this.quotaUsageInBytes; } else { - // json[r'quotaUsageInBytes'] = null; + json[r'quotaUsageInBytes'] = null; } json[r'shouldChangePassword'] = this.shouldChangePassword; json[r'status'] = this.status; if (this.storageLabel != null) { json[r'storageLabel'] = this.storageLabel; } else { - // json[r'storageLabel'] = null; + json[r'storageLabel'] = null; } json[r'updatedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') ? this.updatedAt.millisecondsSinceEpoch diff --git a/mobile/openapi/lib/model/user_admin_update_dto.dart b/mobile/openapi/lib/model/user_admin_update_dto.dart index 0c33a46139..f1b91d8e61 100644 --- a/mobile/openapi/lib/model/user_admin_update_dto.dart +++ b/mobile/openapi/lib/model/user_admin_update_dto.dart @@ -13,18 +13,18 @@ part of openapi.api; class UserAdminUpdateDto { /// Returns a new [UserAdminUpdateDto] instance. UserAdminUpdateDto({ - this.avatarColor, - this.email, - this.isAdmin, - this.name, - this.password, - this.pinCode, - this.quotaSizeInBytes, - this.shouldChangePassword, - this.storageLabel, + this.avatarColor = const Optional.absent(), + this.email = const Optional.absent(), + this.isAdmin = const Optional.absent(), + this.name = const Optional.absent(), + this.password = const Optional.absent(), + this.pinCode = const Optional.absent(), + this.quotaSizeInBytes = const Optional.absent(), + this.shouldChangePassword = const Optional.absent(), + this.storageLabel = const Optional.absent(), }); - UserAvatarColor? avatarColor; + Optional avatarColor; /// User email /// @@ -33,7 +33,7 @@ class UserAdminUpdateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? email; + Optional email; /// Grant admin privileges /// @@ -42,7 +42,7 @@ class UserAdminUpdateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? isAdmin; + Optional isAdmin; /// User name /// @@ -51,7 +51,7 @@ class UserAdminUpdateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? name; + Optional name; /// User password /// @@ -60,16 +60,16 @@ class UserAdminUpdateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? password; + Optional password; /// PIN code - String? pinCode; + Optional pinCode; /// Storage quota in bytes /// /// Minimum value: 0 /// Maximum value: 9007199254740991 - int? quotaSizeInBytes; + Optional quotaSizeInBytes; /// Require password change on next login /// @@ -78,10 +78,10 @@ class UserAdminUpdateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? shouldChangePassword; + Optional shouldChangePassword; /// Storage label - String? storageLabel; + Optional storageLabel; @override bool operator ==(Object other) => identical(this, other) || other is UserAdminUpdateDto && @@ -113,50 +113,41 @@ class UserAdminUpdateDto { Map toJson() { final json = {}; - if (this.avatarColor != null) { - json[r'avatarColor'] = this.avatarColor; - } else { - // json[r'avatarColor'] = null; + if (this.avatarColor.isPresent) { + final value = this.avatarColor.value; + json[r'avatarColor'] = value; } - if (this.email != null) { - json[r'email'] = this.email; - } else { - // json[r'email'] = null; + if (this.email.isPresent) { + final value = this.email.value; + json[r'email'] = value; } - if (this.isAdmin != null) { - json[r'isAdmin'] = this.isAdmin; - } else { - // json[r'isAdmin'] = null; + if (this.isAdmin.isPresent) { + final value = this.isAdmin.value; + json[r'isAdmin'] = value; } - if (this.name != null) { - json[r'name'] = this.name; - } else { - // json[r'name'] = null; + if (this.name.isPresent) { + final value = this.name.value; + json[r'name'] = value; } - if (this.password != null) { - json[r'password'] = this.password; - } else { - // json[r'password'] = null; + if (this.password.isPresent) { + final value = this.password.value; + json[r'password'] = value; } - if (this.pinCode != null) { - json[r'pinCode'] = this.pinCode; - } else { - // json[r'pinCode'] = null; + if (this.pinCode.isPresent) { + final value = this.pinCode.value; + json[r'pinCode'] = value; } - if (this.quotaSizeInBytes != null) { - json[r'quotaSizeInBytes'] = this.quotaSizeInBytes; - } else { - // json[r'quotaSizeInBytes'] = null; + if (this.quotaSizeInBytes.isPresent) { + final value = this.quotaSizeInBytes.value; + json[r'quotaSizeInBytes'] = value; } - if (this.shouldChangePassword != null) { - json[r'shouldChangePassword'] = this.shouldChangePassword; - } else { - // json[r'shouldChangePassword'] = null; + if (this.shouldChangePassword.isPresent) { + final value = this.shouldChangePassword.value; + json[r'shouldChangePassword'] = value; } - if (this.storageLabel != null) { - json[r'storageLabel'] = this.storageLabel; - } else { - // json[r'storageLabel'] = null; + if (this.storageLabel.isPresent) { + final value = this.storageLabel.value; + json[r'storageLabel'] = value; } return json; } @@ -170,15 +161,15 @@ class UserAdminUpdateDto { final json = value.cast(); return UserAdminUpdateDto( - avatarColor: UserAvatarColor.fromJson(json[r'avatarColor']), - email: mapValueOfType(json, r'email'), - isAdmin: mapValueOfType(json, r'isAdmin'), - name: mapValueOfType(json, r'name'), - password: mapValueOfType(json, r'password'), - pinCode: mapValueOfType(json, r'pinCode'), - quotaSizeInBytes: mapValueOfType(json, r'quotaSizeInBytes'), - shouldChangePassword: mapValueOfType(json, r'shouldChangePassword'), - storageLabel: mapValueOfType(json, r'storageLabel'), + avatarColor: json.containsKey(r'avatarColor') ? Optional.present(UserAvatarColor.fromJson(json[r'avatarColor'])) : const Optional.absent(), + email: json.containsKey(r'email') ? Optional.present(mapValueOfType(json, r'email')) : const Optional.absent(), + isAdmin: json.containsKey(r'isAdmin') ? Optional.present(mapValueOfType(json, r'isAdmin')) : const Optional.absent(), + name: json.containsKey(r'name') ? Optional.present(mapValueOfType(json, r'name')) : const Optional.absent(), + password: json.containsKey(r'password') ? Optional.present(mapValueOfType(json, r'password')) : const Optional.absent(), + pinCode: json.containsKey(r'pinCode') ? Optional.present(mapValueOfType(json, r'pinCode')) : const Optional.absent(), + quotaSizeInBytes: json.containsKey(r'quotaSizeInBytes') ? Optional.present(json[r'quotaSizeInBytes'] == null ? null : int.parse('${json[r'quotaSizeInBytes']}')) : const Optional.absent(), + shouldChangePassword: json.containsKey(r'shouldChangePassword') ? Optional.present(mapValueOfType(json, r'shouldChangePassword')) : const Optional.absent(), + storageLabel: json.containsKey(r'storageLabel') ? Optional.present(mapValueOfType(json, r'storageLabel')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/user_preferences_update_dto.dart b/mobile/openapi/lib/model/user_preferences_update_dto.dart index 3b9b178b55..f3f2d85bdf 100644 --- a/mobile/openapi/lib/model/user_preferences_update_dto.dart +++ b/mobile/openapi/lib/model/user_preferences_update_dto.dart @@ -13,18 +13,18 @@ part of openapi.api; class UserPreferencesUpdateDto { /// Returns a new [UserPreferencesUpdateDto] instance. UserPreferencesUpdateDto({ - this.albums, - this.avatar, - this.cast, - this.download, - this.emailNotifications, - this.folders, - this.memories, - this.people, - this.purchase, - this.ratings, - this.sharedLinks, - this.tags, + this.albums = const Optional.absent(), + this.avatar = const Optional.absent(), + this.cast = const Optional.absent(), + this.download = const Optional.absent(), + this.emailNotifications = const Optional.absent(), + this.folders = const Optional.absent(), + this.memories = const Optional.absent(), + this.people = const Optional.absent(), + this.purchase = const Optional.absent(), + this.ratings = const Optional.absent(), + this.sharedLinks = const Optional.absent(), + this.tags = const Optional.absent(), }); /// @@ -33,7 +33,7 @@ class UserPreferencesUpdateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - AlbumsUpdate? albums; + Optional albums; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -41,7 +41,7 @@ class UserPreferencesUpdateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - AvatarUpdate? avatar; + Optional avatar; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -49,7 +49,7 @@ class UserPreferencesUpdateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - CastUpdate? cast; + Optional cast; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -57,7 +57,7 @@ class UserPreferencesUpdateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - DownloadUpdate? download; + Optional download; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -65,7 +65,7 @@ class UserPreferencesUpdateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - EmailNotificationsUpdate? emailNotifications; + Optional emailNotifications; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -73,7 +73,7 @@ class UserPreferencesUpdateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - FoldersUpdate? folders; + Optional folders; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -81,7 +81,7 @@ class UserPreferencesUpdateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - MemoriesUpdate? memories; + Optional memories; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -89,7 +89,7 @@ class UserPreferencesUpdateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - PeopleUpdate? people; + Optional people; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -97,7 +97,7 @@ class UserPreferencesUpdateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - PurchaseUpdate? purchase; + Optional purchase; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -105,7 +105,7 @@ class UserPreferencesUpdateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - RatingsUpdate? ratings; + Optional ratings; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -113,7 +113,7 @@ class UserPreferencesUpdateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - SharedLinksUpdate? sharedLinks; + Optional sharedLinks; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -121,7 +121,7 @@ class UserPreferencesUpdateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - TagsUpdate? tags; + Optional tags; @override bool operator ==(Object other) => identical(this, other) || other is UserPreferencesUpdateDto && @@ -159,65 +159,53 @@ class UserPreferencesUpdateDto { Map toJson() { final json = {}; - if (this.albums != null) { - json[r'albums'] = this.albums; - } else { - // json[r'albums'] = null; + if (this.albums.isPresent) { + final value = this.albums.value; + json[r'albums'] = value; } - if (this.avatar != null) { - json[r'avatar'] = this.avatar; - } else { - // json[r'avatar'] = null; + if (this.avatar.isPresent) { + final value = this.avatar.value; + json[r'avatar'] = value; } - if (this.cast != null) { - json[r'cast'] = this.cast; - } else { - // json[r'cast'] = null; + if (this.cast.isPresent) { + final value = this.cast.value; + json[r'cast'] = value; } - if (this.download != null) { - json[r'download'] = this.download; - } else { - // json[r'download'] = null; + if (this.download.isPresent) { + final value = this.download.value; + json[r'download'] = value; } - if (this.emailNotifications != null) { - json[r'emailNotifications'] = this.emailNotifications; - } else { - // json[r'emailNotifications'] = null; + if (this.emailNotifications.isPresent) { + final value = this.emailNotifications.value; + json[r'emailNotifications'] = value; } - if (this.folders != null) { - json[r'folders'] = this.folders; - } else { - // json[r'folders'] = null; + if (this.folders.isPresent) { + final value = this.folders.value; + json[r'folders'] = value; } - if (this.memories != null) { - json[r'memories'] = this.memories; - } else { - // json[r'memories'] = null; + if (this.memories.isPresent) { + final value = this.memories.value; + json[r'memories'] = value; } - if (this.people != null) { - json[r'people'] = this.people; - } else { - // json[r'people'] = null; + if (this.people.isPresent) { + final value = this.people.value; + json[r'people'] = value; } - if (this.purchase != null) { - json[r'purchase'] = this.purchase; - } else { - // json[r'purchase'] = null; + if (this.purchase.isPresent) { + final value = this.purchase.value; + json[r'purchase'] = value; } - if (this.ratings != null) { - json[r'ratings'] = this.ratings; - } else { - // json[r'ratings'] = null; + if (this.ratings.isPresent) { + final value = this.ratings.value; + json[r'ratings'] = value; } - if (this.sharedLinks != null) { - json[r'sharedLinks'] = this.sharedLinks; - } else { - // json[r'sharedLinks'] = null; + if (this.sharedLinks.isPresent) { + final value = this.sharedLinks.value; + json[r'sharedLinks'] = value; } - if (this.tags != null) { - json[r'tags'] = this.tags; - } else { - // json[r'tags'] = null; + if (this.tags.isPresent) { + final value = this.tags.value; + json[r'tags'] = value; } return json; } @@ -231,18 +219,18 @@ class UserPreferencesUpdateDto { final json = value.cast(); return UserPreferencesUpdateDto( - albums: AlbumsUpdate.fromJson(json[r'albums']), - avatar: AvatarUpdate.fromJson(json[r'avatar']), - cast: CastUpdate.fromJson(json[r'cast']), - download: DownloadUpdate.fromJson(json[r'download']), - emailNotifications: EmailNotificationsUpdate.fromJson(json[r'emailNotifications']), - folders: FoldersUpdate.fromJson(json[r'folders']), - memories: MemoriesUpdate.fromJson(json[r'memories']), - people: PeopleUpdate.fromJson(json[r'people']), - purchase: PurchaseUpdate.fromJson(json[r'purchase']), - ratings: RatingsUpdate.fromJson(json[r'ratings']), - sharedLinks: SharedLinksUpdate.fromJson(json[r'sharedLinks']), - tags: TagsUpdate.fromJson(json[r'tags']), + albums: json.containsKey(r'albums') ? Optional.present(AlbumsUpdate.fromJson(json[r'albums'])) : const Optional.absent(), + avatar: json.containsKey(r'avatar') ? Optional.present(AvatarUpdate.fromJson(json[r'avatar'])) : const Optional.absent(), + cast: json.containsKey(r'cast') ? Optional.present(CastUpdate.fromJson(json[r'cast'])) : const Optional.absent(), + download: json.containsKey(r'download') ? Optional.present(DownloadUpdate.fromJson(json[r'download'])) : const Optional.absent(), + emailNotifications: json.containsKey(r'emailNotifications') ? Optional.present(EmailNotificationsUpdate.fromJson(json[r'emailNotifications'])) : const Optional.absent(), + folders: json.containsKey(r'folders') ? Optional.present(FoldersUpdate.fromJson(json[r'folders'])) : const Optional.absent(), + memories: json.containsKey(r'memories') ? Optional.present(MemoriesUpdate.fromJson(json[r'memories'])) : const Optional.absent(), + people: json.containsKey(r'people') ? Optional.present(PeopleUpdate.fromJson(json[r'people'])) : const Optional.absent(), + purchase: json.containsKey(r'purchase') ? Optional.present(PurchaseUpdate.fromJson(json[r'purchase'])) : const Optional.absent(), + ratings: json.containsKey(r'ratings') ? Optional.present(RatingsUpdate.fromJson(json[r'ratings'])) : const Optional.absent(), + sharedLinks: json.containsKey(r'sharedLinks') ? Optional.present(SharedLinksUpdate.fromJson(json[r'sharedLinks'])) : const Optional.absent(), + tags: json.containsKey(r'tags') ? Optional.present(TagsUpdate.fromJson(json[r'tags'])) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/user_update_me_dto.dart b/mobile/openapi/lib/model/user_update_me_dto.dart index 0751d4096b..c4859747a6 100644 --- a/mobile/openapi/lib/model/user_update_me_dto.dart +++ b/mobile/openapi/lib/model/user_update_me_dto.dart @@ -13,13 +13,13 @@ part of openapi.api; class UserUpdateMeDto { /// Returns a new [UserUpdateMeDto] instance. UserUpdateMeDto({ - this.avatarColor, - this.email, - this.name, - this.password, + this.avatarColor = const Optional.absent(), + this.email = const Optional.absent(), + this.name = const Optional.absent(), + this.password = const Optional.absent(), }); - UserAvatarColor? avatarColor; + Optional avatarColor; /// User email /// @@ -28,7 +28,7 @@ class UserUpdateMeDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? email; + Optional email; /// User name /// @@ -37,7 +37,7 @@ class UserUpdateMeDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? name; + Optional name; /// User password (deprecated, use change password endpoint) /// @@ -46,7 +46,7 @@ class UserUpdateMeDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? password; + Optional password; @override bool operator ==(Object other) => identical(this, other) || other is UserUpdateMeDto && @@ -68,25 +68,21 @@ class UserUpdateMeDto { Map toJson() { final json = {}; - if (this.avatarColor != null) { - json[r'avatarColor'] = this.avatarColor; - } else { - // json[r'avatarColor'] = null; + if (this.avatarColor.isPresent) { + final value = this.avatarColor.value; + json[r'avatarColor'] = value; } - if (this.email != null) { - json[r'email'] = this.email; - } else { - // json[r'email'] = null; + if (this.email.isPresent) { + final value = this.email.value; + json[r'email'] = value; } - if (this.name != null) { - json[r'name'] = this.name; - } else { - // json[r'name'] = null; + if (this.name.isPresent) { + final value = this.name.value; + json[r'name'] = value; } - if (this.password != null) { - json[r'password'] = this.password; - } else { - // json[r'password'] = null; + if (this.password.isPresent) { + final value = this.password.value; + json[r'password'] = value; } return json; } @@ -100,10 +96,10 @@ class UserUpdateMeDto { final json = value.cast(); return UserUpdateMeDto( - avatarColor: UserAvatarColor.fromJson(json[r'avatarColor']), - email: mapValueOfType(json, r'email'), - name: mapValueOfType(json, r'name'), - password: mapValueOfType(json, r'password'), + avatarColor: json.containsKey(r'avatarColor') ? Optional.present(UserAvatarColor.fromJson(json[r'avatarColor'])) : const Optional.absent(), + email: json.containsKey(r'email') ? Optional.present(mapValueOfType(json, r'email')) : const Optional.absent(), + name: json.containsKey(r'name') ? Optional.present(mapValueOfType(json, r'name')) : const Optional.absent(), + password: json.containsKey(r'password') ? Optional.present(mapValueOfType(json, r'password')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/validate_library_dto.dart b/mobile/openapi/lib/model/validate_library_dto.dart index 68fb0e9fe2..6d85712191 100644 --- a/mobile/openapi/lib/model/validate_library_dto.dart +++ b/mobile/openapi/lib/model/validate_library_dto.dart @@ -13,15 +13,15 @@ part of openapi.api; class ValidateLibraryDto { /// Returns a new [ValidateLibraryDto] instance. ValidateLibraryDto({ - this.exclusionPatterns = const [], - this.importPaths = const [], + this.exclusionPatterns = const Optional.present(const []), + this.importPaths = const Optional.present(const []), }); /// Exclusion patterns (max 128) - List exclusionPatterns; + Optional?> exclusionPatterns; /// Import paths to validate (max 128) - List importPaths; + Optional?> importPaths; @override bool operator ==(Object other) => identical(this, other) || other is ValidateLibraryDto && @@ -39,8 +39,14 @@ class ValidateLibraryDto { Map toJson() { final json = {}; - json[r'exclusionPatterns'] = this.exclusionPatterns; - json[r'importPaths'] = this.importPaths; + if (this.exclusionPatterns.isPresent) { + final value = this.exclusionPatterns.value; + json[r'exclusionPatterns'] = value; + } + if (this.importPaths.isPresent) { + final value = this.importPaths.value; + json[r'importPaths'] = value; + } return json; } @@ -53,12 +59,12 @@ class ValidateLibraryDto { final json = value.cast(); return ValidateLibraryDto( - exclusionPatterns: json[r'exclusionPatterns'] is Iterable + exclusionPatterns: json.containsKey(r'exclusionPatterns') ? Optional.present(json[r'exclusionPatterns'] is Iterable ? (json[r'exclusionPatterns'] as Iterable).cast().toList(growable: false) - : const [], - importPaths: json[r'importPaths'] is Iterable + : const []) : const Optional.absent(), + importPaths: json.containsKey(r'importPaths') ? Optional.present(json[r'importPaths'] is Iterable ? (json[r'importPaths'] as Iterable).cast().toList(growable: false) - : const [], + : const []) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/validate_library_import_path_response_dto.dart b/mobile/openapi/lib/model/validate_library_import_path_response_dto.dart index ebcb881935..3c8d7c58cd 100644 --- a/mobile/openapi/lib/model/validate_library_import_path_response_dto.dart +++ b/mobile/openapi/lib/model/validate_library_import_path_response_dto.dart @@ -15,7 +15,7 @@ class ValidateLibraryImportPathResponseDto { ValidateLibraryImportPathResponseDto({ required this.importPath, required this.isValid, - this.message, + this.message = const Optional.absent(), }); /// Import path @@ -31,7 +31,7 @@ class ValidateLibraryImportPathResponseDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? message; + Optional message; @override bool operator ==(Object other) => identical(this, other) || other is ValidateLibraryImportPathResponseDto && @@ -53,10 +53,9 @@ class ValidateLibraryImportPathResponseDto { final json = {}; json[r'importPath'] = this.importPath; json[r'isValid'] = this.isValid; - if (this.message != null) { - json[r'message'] = this.message; - } else { - // json[r'message'] = null; + if (this.message.isPresent) { + final value = this.message.value; + json[r'message'] = value; } return json; } @@ -72,7 +71,7 @@ class ValidateLibraryImportPathResponseDto { return ValidateLibraryImportPathResponseDto( importPath: mapValueOfType(json, r'importPath')!, isValid: mapValueOfType(json, r'isValid')!, - message: mapValueOfType(json, r'message'), + message: json.containsKey(r'message') ? Optional.present(mapValueOfType(json, r'message')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/validate_library_response_dto.dart b/mobile/openapi/lib/model/validate_library_response_dto.dart index 37f6ad07d1..5106b93ca8 100644 --- a/mobile/openapi/lib/model/validate_library_response_dto.dart +++ b/mobile/openapi/lib/model/validate_library_response_dto.dart @@ -13,11 +13,11 @@ part of openapi.api; class ValidateLibraryResponseDto { /// Returns a new [ValidateLibraryResponseDto] instance. ValidateLibraryResponseDto({ - this.importPaths = const [], + this.importPaths = const Optional.present(const []), }); /// Validation results for import paths - List importPaths; + Optional?> importPaths; @override bool operator ==(Object other) => identical(this, other) || other is ValidateLibraryResponseDto && @@ -33,7 +33,10 @@ class ValidateLibraryResponseDto { Map toJson() { final json = {}; - json[r'importPaths'] = this.importPaths; + if (this.importPaths.isPresent) { + final value = this.importPaths.value; + json[r'importPaths'] = value; + } return json; } @@ -46,7 +49,7 @@ class ValidateLibraryResponseDto { final json = value.cast(); return ValidateLibraryResponseDto( - importPaths: ValidateLibraryImportPathResponseDto.listFromJson(json[r'importPaths']), + importPaths: json.containsKey(r'importPaths') ? Optional.present(ValidateLibraryImportPathResponseDto.listFromJson(json[r'importPaths'])) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/version_check_state_response_dto.dart b/mobile/openapi/lib/model/version_check_state_response_dto.dart index 71075a681c..4ad9458a1b 100644 --- a/mobile/openapi/lib/model/version_check_state_response_dto.dart +++ b/mobile/openapi/lib/model/version_check_state_response_dto.dart @@ -42,12 +42,12 @@ class VersionCheckStateResponseDto { if (this.checkedAt != null) { json[r'checkedAt'] = this.checkedAt; } else { - // json[r'checkedAt'] = null; + json[r'checkedAt'] = null; } if (this.releaseVersion != null) { json[r'releaseVersion'] = this.releaseVersion; } else { - // json[r'releaseVersion'] = null; + json[r'releaseVersion'] = null; } return json; } diff --git a/mobile/openapi/lib/model/workflow_action_item_dto.dart b/mobile/openapi/lib/model/workflow_action_item_dto.dart deleted file mode 100644 index 1ad70238d8..0000000000 --- a/mobile/openapi/lib/model/workflow_action_item_dto.dart +++ /dev/null @@ -1,107 +0,0 @@ -// -// AUTO-GENERATED FILE, DO NOT MODIFY! -// -// @dart=2.18 - -// ignore_for_file: unused_element, unused_import -// ignore_for_file: always_put_required_named_parameters_first -// ignore_for_file: constant_identifier_names -// ignore_for_file: lines_longer_than_80_chars - -part of openapi.api; - -class WorkflowActionItemDto { - /// Returns a new [WorkflowActionItemDto] instance. - WorkflowActionItemDto({ - this.actionConfig = const {}, - required this.pluginActionId, - }); - - Map actionConfig; - - /// Plugin action ID - String pluginActionId; - - @override - bool operator ==(Object other) => identical(this, other) || other is WorkflowActionItemDto && - _deepEquality.equals(other.actionConfig, actionConfig) && - other.pluginActionId == pluginActionId; - - @override - int get hashCode => - // ignore: unnecessary_parenthesis - (actionConfig.hashCode) + - (pluginActionId.hashCode); - - @override - String toString() => 'WorkflowActionItemDto[actionConfig=$actionConfig, pluginActionId=$pluginActionId]'; - - Map toJson() { - final json = {}; - json[r'actionConfig'] = this.actionConfig; - json[r'pluginActionId'] = this.pluginActionId; - return json; - } - - /// Returns a new [WorkflowActionItemDto] instance and imports its values from - /// [value] if it's a [Map], null otherwise. - // ignore: prefer_constructors_over_static_methods - static WorkflowActionItemDto? fromJson(dynamic value) { - upgradeDto(value, "WorkflowActionItemDto"); - if (value is Map) { - final json = value.cast(); - - return WorkflowActionItemDto( - actionConfig: mapCastOfType(json, r'actionConfig') ?? const {}, - pluginActionId: mapValueOfType(json, r'pluginActionId')!, - ); - } - return null; - } - - static List listFromJson(dynamic json, {bool growable = false,}) { - final result = []; - if (json is List && json.isNotEmpty) { - for (final row in json) { - final value = WorkflowActionItemDto.fromJson(row); - if (value != null) { - result.add(value); - } - } - } - return result.toList(growable: growable); - } - - static Map mapFromJson(dynamic json) { - final map = {}; - if (json is Map && json.isNotEmpty) { - json = json.cast(); // ignore: parameter_assignments - for (final entry in json.entries) { - final value = WorkflowActionItemDto.fromJson(entry.value); - if (value != null) { - map[entry.key] = value; - } - } - } - return map; - } - - // maps a json object with a list of WorkflowActionItemDto-objects as value to a dart map - static Map> mapListFromJson(dynamic json, {bool growable = false,}) { - final map = >{}; - if (json is Map && json.isNotEmpty) { - // ignore: parameter_assignments - json = json.cast(); - for (final entry in json.entries) { - map[entry.key] = WorkflowActionItemDto.listFromJson(entry.value, growable: growable,); - } - } - return map; - } - - /// The list of required keys that must be present in a JSON. - static const requiredKeys = { - 'pluginActionId', - }; -} - diff --git a/mobile/openapi/lib/model/workflow_action_response_dto.dart b/mobile/openapi/lib/model/workflow_action_response_dto.dart deleted file mode 100644 index 999d9d86cb..0000000000 --- a/mobile/openapi/lib/model/workflow_action_response_dto.dart +++ /dev/null @@ -1,142 +0,0 @@ -// -// AUTO-GENERATED FILE, DO NOT MODIFY! -// -// @dart=2.18 - -// ignore_for_file: unused_element, unused_import -// ignore_for_file: always_put_required_named_parameters_first -// ignore_for_file: constant_identifier_names -// ignore_for_file: lines_longer_than_80_chars - -part of openapi.api; - -class WorkflowActionResponseDto { - /// Returns a new [WorkflowActionResponseDto] instance. - WorkflowActionResponseDto({ - required this.actionConfig, - required this.id, - required this.order, - required this.pluginActionId, - required this.workflowId, - }); - - Map? actionConfig; - - /// Action ID - String id; - - /// Action order - /// - /// Minimum value: -9007199254740991 - /// Maximum value: 9007199254740991 - int order; - - /// Plugin action ID - String pluginActionId; - - /// Workflow ID - String workflowId; - - @override - bool operator ==(Object other) => identical(this, other) || other is WorkflowActionResponseDto && - _deepEquality.equals(other.actionConfig, actionConfig) && - other.id == id && - other.order == order && - other.pluginActionId == pluginActionId && - other.workflowId == workflowId; - - @override - int get hashCode => - // ignore: unnecessary_parenthesis - (actionConfig == null ? 0 : actionConfig!.hashCode) + - (id.hashCode) + - (order.hashCode) + - (pluginActionId.hashCode) + - (workflowId.hashCode); - - @override - String toString() => 'WorkflowActionResponseDto[actionConfig=$actionConfig, id=$id, order=$order, pluginActionId=$pluginActionId, workflowId=$workflowId]'; - - Map toJson() { - final json = {}; - if (this.actionConfig != null) { - json[r'actionConfig'] = this.actionConfig; - } else { - // json[r'actionConfig'] = null; - } - json[r'id'] = this.id; - json[r'order'] = this.order; - json[r'pluginActionId'] = this.pluginActionId; - json[r'workflowId'] = this.workflowId; - return json; - } - - /// Returns a new [WorkflowActionResponseDto] instance and imports its values from - /// [value] if it's a [Map], null otherwise. - // ignore: prefer_constructors_over_static_methods - static WorkflowActionResponseDto? fromJson(dynamic value) { - upgradeDto(value, "WorkflowActionResponseDto"); - if (value is Map) { - final json = value.cast(); - - return WorkflowActionResponseDto( - actionConfig: mapCastOfType(json, r'actionConfig'), - id: mapValueOfType(json, r'id')!, - order: mapValueOfType(json, r'order')!, - pluginActionId: mapValueOfType(json, r'pluginActionId')!, - workflowId: mapValueOfType(json, r'workflowId')!, - ); - } - return null; - } - - static List listFromJson(dynamic json, {bool growable = false,}) { - final result = []; - if (json is List && json.isNotEmpty) { - for (final row in json) { - final value = WorkflowActionResponseDto.fromJson(row); - if (value != null) { - result.add(value); - } - } - } - return result.toList(growable: growable); - } - - static Map mapFromJson(dynamic json) { - final map = {}; - if (json is Map && json.isNotEmpty) { - json = json.cast(); // ignore: parameter_assignments - for (final entry in json.entries) { - final value = WorkflowActionResponseDto.fromJson(entry.value); - if (value != null) { - map[entry.key] = value; - } - } - } - return map; - } - - // maps a json object with a list of WorkflowActionResponseDto-objects as value to a dart map - static Map> mapListFromJson(dynamic json, {bool growable = false,}) { - final map = >{}; - if (json is Map && json.isNotEmpty) { - // ignore: parameter_assignments - json = json.cast(); - for (final entry in json.entries) { - map[entry.key] = WorkflowActionResponseDto.listFromJson(entry.value, growable: growable,); - } - } - return map; - } - - /// The list of required keys that must be present in a JSON. - static const requiredKeys = { - 'actionConfig', - 'id', - 'order', - 'pluginActionId', - 'workflowId', - }; -} - diff --git a/mobile/openapi/lib/model/workflow_create_dto.dart b/mobile/openapi/lib/model/workflow_create_dto.dart index 143af0ca6c..e84554e8c9 100644 --- a/mobile/openapi/lib/model/workflow_create_dto.dart +++ b/mobile/openapi/lib/model/workflow_create_dto.dart @@ -13,25 +13,15 @@ part of openapi.api; class WorkflowCreateDto { /// Returns a new [WorkflowCreateDto] instance. WorkflowCreateDto({ - this.actions = const [], - this.description, - this.enabled, - this.filters = const [], - required this.name, - required this.triggerType, + this.description = const Optional.absent(), + this.enabled = const Optional.absent(), + this.name = const Optional.absent(), + this.steps = const Optional.present(const []), + required this.trigger, }); - /// Workflow actions - List actions; - /// Workflow description - /// - /// Please note: This property should have been non-nullable! Since the specification file - /// does not include a default value (using the "default:" property), however, the generated - /// source code must fall back to having a nullable type. - /// Consider adding a "default:" property in the specification file to hide this note. - /// - String? description; + Optional description; /// Workflow enabled /// @@ -40,54 +30,54 @@ class WorkflowCreateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? enabled; - - /// Workflow filters - List filters; + Optional enabled; /// Workflow name - String name; + Optional name; - PluginTriggerType triggerType; + Optional?> steps; + + WorkflowTrigger trigger; @override bool operator ==(Object other) => identical(this, other) || other is WorkflowCreateDto && - _deepEquality.equals(other.actions, actions) && other.description == description && other.enabled == enabled && - _deepEquality.equals(other.filters, filters) && other.name == name && - other.triggerType == triggerType; + _deepEquality.equals(other.steps, steps) && + other.trigger == trigger; @override int get hashCode => // ignore: unnecessary_parenthesis - (actions.hashCode) + (description == null ? 0 : description!.hashCode) + (enabled == null ? 0 : enabled!.hashCode) + - (filters.hashCode) + - (name.hashCode) + - (triggerType.hashCode); + (name == null ? 0 : name!.hashCode) + + (steps.hashCode) + + (trigger.hashCode); @override - String toString() => 'WorkflowCreateDto[actions=$actions, description=$description, enabled=$enabled, filters=$filters, name=$name, triggerType=$triggerType]'; + String toString() => 'WorkflowCreateDto[description=$description, enabled=$enabled, name=$name, steps=$steps, trigger=$trigger]'; Map toJson() { final json = {}; - json[r'actions'] = this.actions; - if (this.description != null) { - json[r'description'] = this.description; - } else { - // json[r'description'] = null; + if (this.description.isPresent) { + final value = this.description.value; + json[r'description'] = value; } - if (this.enabled != null) { - json[r'enabled'] = this.enabled; - } else { - // json[r'enabled'] = null; + if (this.enabled.isPresent) { + final value = this.enabled.value; + json[r'enabled'] = value; } - json[r'filters'] = this.filters; - json[r'name'] = this.name; - json[r'triggerType'] = this.triggerType; + if (this.name.isPresent) { + final value = this.name.value; + json[r'name'] = value; + } + if (this.steps.isPresent) { + final value = this.steps.value; + json[r'steps'] = value; + } + json[r'trigger'] = this.trigger; return json; } @@ -100,12 +90,11 @@ class WorkflowCreateDto { final json = value.cast(); return WorkflowCreateDto( - actions: WorkflowActionItemDto.listFromJson(json[r'actions']), - description: mapValueOfType(json, r'description'), - enabled: mapValueOfType(json, r'enabled'), - filters: WorkflowFilterItemDto.listFromJson(json[r'filters']), - name: mapValueOfType(json, r'name')!, - triggerType: PluginTriggerType.fromJson(json[r'triggerType'])!, + description: json.containsKey(r'description') ? Optional.present(mapValueOfType(json, r'description')) : const Optional.absent(), + enabled: json.containsKey(r'enabled') ? Optional.present(mapValueOfType(json, r'enabled')) : const Optional.absent(), + name: json.containsKey(r'name') ? Optional.present(mapValueOfType(json, r'name')) : const Optional.absent(), + steps: json.containsKey(r'steps') ? Optional.present(WorkflowStepDto.listFromJson(json[r'steps'])) : const Optional.absent(), + trigger: WorkflowTrigger.fromJson(json[r'trigger'])!, ); } return null; @@ -153,10 +142,7 @@ class WorkflowCreateDto { /// The list of required keys that must be present in a JSON. static const requiredKeys = { - 'actions', - 'filters', - 'name', - 'triggerType', + 'trigger', }; } diff --git a/mobile/openapi/lib/model/workflow_filter_item_dto.dart b/mobile/openapi/lib/model/workflow_filter_item_dto.dart deleted file mode 100644 index 92224b9f16..0000000000 --- a/mobile/openapi/lib/model/workflow_filter_item_dto.dart +++ /dev/null @@ -1,107 +0,0 @@ -// -// AUTO-GENERATED FILE, DO NOT MODIFY! -// -// @dart=2.18 - -// ignore_for_file: unused_element, unused_import -// ignore_for_file: always_put_required_named_parameters_first -// ignore_for_file: constant_identifier_names -// ignore_for_file: lines_longer_than_80_chars - -part of openapi.api; - -class WorkflowFilterItemDto { - /// Returns a new [WorkflowFilterItemDto] instance. - WorkflowFilterItemDto({ - this.filterConfig = const {}, - required this.pluginFilterId, - }); - - Map filterConfig; - - /// Plugin filter ID - String pluginFilterId; - - @override - bool operator ==(Object other) => identical(this, other) || other is WorkflowFilterItemDto && - _deepEquality.equals(other.filterConfig, filterConfig) && - other.pluginFilterId == pluginFilterId; - - @override - int get hashCode => - // ignore: unnecessary_parenthesis - (filterConfig.hashCode) + - (pluginFilterId.hashCode); - - @override - String toString() => 'WorkflowFilterItemDto[filterConfig=$filterConfig, pluginFilterId=$pluginFilterId]'; - - Map toJson() { - final json = {}; - json[r'filterConfig'] = this.filterConfig; - json[r'pluginFilterId'] = this.pluginFilterId; - return json; - } - - /// Returns a new [WorkflowFilterItemDto] instance and imports its values from - /// [value] if it's a [Map], null otherwise. - // ignore: prefer_constructors_over_static_methods - static WorkflowFilterItemDto? fromJson(dynamic value) { - upgradeDto(value, "WorkflowFilterItemDto"); - if (value is Map) { - final json = value.cast(); - - return WorkflowFilterItemDto( - filterConfig: mapCastOfType(json, r'filterConfig') ?? const {}, - pluginFilterId: mapValueOfType(json, r'pluginFilterId')!, - ); - } - return null; - } - - static List listFromJson(dynamic json, {bool growable = false,}) { - final result = []; - if (json is List && json.isNotEmpty) { - for (final row in json) { - final value = WorkflowFilterItemDto.fromJson(row); - if (value != null) { - result.add(value); - } - } - } - return result.toList(growable: growable); - } - - static Map mapFromJson(dynamic json) { - final map = {}; - if (json is Map && json.isNotEmpty) { - json = json.cast(); // ignore: parameter_assignments - for (final entry in json.entries) { - final value = WorkflowFilterItemDto.fromJson(entry.value); - if (value != null) { - map[entry.key] = value; - } - } - } - return map; - } - - // maps a json object with a list of WorkflowFilterItemDto-objects as value to a dart map - static Map> mapListFromJson(dynamic json, {bool growable = false,}) { - final map = >{}; - if (json is Map && json.isNotEmpty) { - // ignore: parameter_assignments - json = json.cast(); - for (final entry in json.entries) { - map[entry.key] = WorkflowFilterItemDto.listFromJson(entry.value, growable: growable,); - } - } - return map; - } - - /// The list of required keys that must be present in a JSON. - static const requiredKeys = { - 'pluginFilterId', - }; -} - diff --git a/mobile/openapi/lib/model/workflow_filter_response_dto.dart b/mobile/openapi/lib/model/workflow_filter_response_dto.dart deleted file mode 100644 index b9a841a68b..0000000000 --- a/mobile/openapi/lib/model/workflow_filter_response_dto.dart +++ /dev/null @@ -1,142 +0,0 @@ -// -// AUTO-GENERATED FILE, DO NOT MODIFY! -// -// @dart=2.18 - -// ignore_for_file: unused_element, unused_import -// ignore_for_file: always_put_required_named_parameters_first -// ignore_for_file: constant_identifier_names -// ignore_for_file: lines_longer_than_80_chars - -part of openapi.api; - -class WorkflowFilterResponseDto { - /// Returns a new [WorkflowFilterResponseDto] instance. - WorkflowFilterResponseDto({ - required this.filterConfig, - required this.id, - required this.order, - required this.pluginFilterId, - required this.workflowId, - }); - - Map? filterConfig; - - /// Filter ID - String id; - - /// Filter order - /// - /// Minimum value: -9007199254740991 - /// Maximum value: 9007199254740991 - int order; - - /// Plugin filter ID - String pluginFilterId; - - /// Workflow ID - String workflowId; - - @override - bool operator ==(Object other) => identical(this, other) || other is WorkflowFilterResponseDto && - _deepEquality.equals(other.filterConfig, filterConfig) && - other.id == id && - other.order == order && - other.pluginFilterId == pluginFilterId && - other.workflowId == workflowId; - - @override - int get hashCode => - // ignore: unnecessary_parenthesis - (filterConfig == null ? 0 : filterConfig!.hashCode) + - (id.hashCode) + - (order.hashCode) + - (pluginFilterId.hashCode) + - (workflowId.hashCode); - - @override - String toString() => 'WorkflowFilterResponseDto[filterConfig=$filterConfig, id=$id, order=$order, pluginFilterId=$pluginFilterId, workflowId=$workflowId]'; - - Map toJson() { - final json = {}; - if (this.filterConfig != null) { - json[r'filterConfig'] = this.filterConfig; - } else { - // json[r'filterConfig'] = null; - } - json[r'id'] = this.id; - json[r'order'] = this.order; - json[r'pluginFilterId'] = this.pluginFilterId; - json[r'workflowId'] = this.workflowId; - return json; - } - - /// Returns a new [WorkflowFilterResponseDto] instance and imports its values from - /// [value] if it's a [Map], null otherwise. - // ignore: prefer_constructors_over_static_methods - static WorkflowFilterResponseDto? fromJson(dynamic value) { - upgradeDto(value, "WorkflowFilterResponseDto"); - if (value is Map) { - final json = value.cast(); - - return WorkflowFilterResponseDto( - filterConfig: mapCastOfType(json, r'filterConfig'), - id: mapValueOfType(json, r'id')!, - order: mapValueOfType(json, r'order')!, - pluginFilterId: mapValueOfType(json, r'pluginFilterId')!, - workflowId: mapValueOfType(json, r'workflowId')!, - ); - } - return null; - } - - static List listFromJson(dynamic json, {bool growable = false,}) { - final result = []; - if (json is List && json.isNotEmpty) { - for (final row in json) { - final value = WorkflowFilterResponseDto.fromJson(row); - if (value != null) { - result.add(value); - } - } - } - return result.toList(growable: growable); - } - - static Map mapFromJson(dynamic json) { - final map = {}; - if (json is Map && json.isNotEmpty) { - json = json.cast(); // ignore: parameter_assignments - for (final entry in json.entries) { - final value = WorkflowFilterResponseDto.fromJson(entry.value); - if (value != null) { - map[entry.key] = value; - } - } - } - return map; - } - - // maps a json object with a list of WorkflowFilterResponseDto-objects as value to a dart map - static Map> mapListFromJson(dynamic json, {bool growable = false,}) { - final map = >{}; - if (json is Map && json.isNotEmpty) { - // ignore: parameter_assignments - json = json.cast(); - for (final entry in json.entries) { - map[entry.key] = WorkflowFilterResponseDto.listFromJson(entry.value, growable: growable,); - } - } - return map; - } - - /// The list of required keys that must be present in a JSON. - static const requiredKeys = { - 'filterConfig', - 'id', - 'order', - 'pluginFilterId', - 'workflowId', - }; -} - diff --git a/mobile/openapi/lib/model/workflow_response_dto.dart b/mobile/openapi/lib/model/workflow_response_dto.dart index 6461b62508..b2b5586601 100644 --- a/mobile/openapi/lib/model/workflow_response_dto.dart +++ b/mobile/openapi/lib/model/workflow_response_dto.dart @@ -13,86 +13,83 @@ part of openapi.api; class WorkflowResponseDto { /// Returns a new [WorkflowResponseDto] instance. WorkflowResponseDto({ - this.actions = const [], required this.createdAt, required this.description, required this.enabled, - this.filters = const [], required this.id, required this.name, - required this.ownerId, - required this.triggerType, + this.steps = const [], + required this.trigger, + required this.updatedAt, }); - /// Workflow actions - List actions; - /// Creation date String createdAt; /// Workflow description - String description; + String? description; /// Workflow enabled bool enabled; - /// Workflow filters - List filters; - /// Workflow ID String id; /// Workflow name String? name; - /// Owner user ID - String ownerId; + /// Workflow steps + List steps; - PluginTriggerType triggerType; + WorkflowTrigger trigger; + + /// Update date + String updatedAt; @override bool operator ==(Object other) => identical(this, other) || other is WorkflowResponseDto && - _deepEquality.equals(other.actions, actions) && other.createdAt == createdAt && other.description == description && other.enabled == enabled && - _deepEquality.equals(other.filters, filters) && other.id == id && other.name == name && - other.ownerId == ownerId && - other.triggerType == triggerType; + _deepEquality.equals(other.steps, steps) && + other.trigger == trigger && + other.updatedAt == updatedAt; @override int get hashCode => // ignore: unnecessary_parenthesis - (actions.hashCode) + (createdAt.hashCode) + - (description.hashCode) + + (description == null ? 0 : description!.hashCode) + (enabled.hashCode) + - (filters.hashCode) + (id.hashCode) + (name == null ? 0 : name!.hashCode) + - (ownerId.hashCode) + - (triggerType.hashCode); + (steps.hashCode) + + (trigger.hashCode) + + (updatedAt.hashCode); @override - String toString() => 'WorkflowResponseDto[actions=$actions, createdAt=$createdAt, description=$description, enabled=$enabled, filters=$filters, id=$id, name=$name, ownerId=$ownerId, triggerType=$triggerType]'; + String toString() => 'WorkflowResponseDto[createdAt=$createdAt, description=$description, enabled=$enabled, id=$id, name=$name, steps=$steps, trigger=$trigger, updatedAt=$updatedAt]'; Map toJson() { final json = {}; - json[r'actions'] = this.actions; json[r'createdAt'] = this.createdAt; + if (this.description != null) { json[r'description'] = this.description; + } else { + json[r'description'] = null; + } json[r'enabled'] = this.enabled; - json[r'filters'] = this.filters; json[r'id'] = this.id; if (this.name != null) { json[r'name'] = this.name; } else { - // json[r'name'] = null; + json[r'name'] = null; } - json[r'ownerId'] = this.ownerId; - json[r'triggerType'] = this.triggerType; + json[r'steps'] = this.steps; + json[r'trigger'] = this.trigger; + json[r'updatedAt'] = this.updatedAt; return json; } @@ -105,15 +102,14 @@ class WorkflowResponseDto { final json = value.cast(); return WorkflowResponseDto( - actions: WorkflowActionResponseDto.listFromJson(json[r'actions']), createdAt: mapValueOfType(json, r'createdAt')!, - description: mapValueOfType(json, r'description')!, + description: mapValueOfType(json, r'description'), enabled: mapValueOfType(json, r'enabled')!, - filters: WorkflowFilterResponseDto.listFromJson(json[r'filters']), id: mapValueOfType(json, r'id')!, name: mapValueOfType(json, r'name'), - ownerId: mapValueOfType(json, r'ownerId')!, - triggerType: PluginTriggerType.fromJson(json[r'triggerType'])!, + steps: WorkflowStepDto.listFromJson(json[r'steps']), + trigger: WorkflowTrigger.fromJson(json[r'trigger'])!, + updatedAt: mapValueOfType(json, r'updatedAt')!, ); } return null; @@ -161,15 +157,14 @@ class WorkflowResponseDto { /// The list of required keys that must be present in a JSON. static const requiredKeys = { - 'actions', 'createdAt', 'description', 'enabled', - 'filters', 'id', 'name', - 'ownerId', - 'triggerType', + 'steps', + 'trigger', + 'updatedAt', }; } diff --git a/mobile/openapi/lib/model/workflow_share_response_dto.dart b/mobile/openapi/lib/model/workflow_share_response_dto.dart new file mode 100644 index 0000000000..d7e90085c2 --- /dev/null +++ b/mobile/openapi/lib/model/workflow_share_response_dto.dart @@ -0,0 +1,134 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class WorkflowShareResponseDto { + /// Returns a new [WorkflowShareResponseDto] instance. + WorkflowShareResponseDto({ + required this.description, + required this.name, + this.steps = const [], + required this.trigger, + }); + + /// Workflow description + String? description; + + /// Workflow name + String? name; + + /// Workflow steps + List steps; + + WorkflowTrigger trigger; + + @override + bool operator ==(Object other) => identical(this, other) || other is WorkflowShareResponseDto && + other.description == description && + other.name == name && + _deepEquality.equals(other.steps, steps) && + other.trigger == trigger; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (description == null ? 0 : description!.hashCode) + + (name == null ? 0 : name!.hashCode) + + (steps.hashCode) + + (trigger.hashCode); + + @override + String toString() => 'WorkflowShareResponseDto[description=$description, name=$name, steps=$steps, trigger=$trigger]'; + + Map toJson() { + final json = {}; + if (this.description != null) { + json[r'description'] = this.description; + } else { + json[r'description'] = null; + } + if (this.name != null) { + json[r'name'] = this.name; + } else { + json[r'name'] = null; + } + json[r'steps'] = this.steps; + json[r'trigger'] = this.trigger; + return json; + } + + /// Returns a new [WorkflowShareResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static WorkflowShareResponseDto? fromJson(dynamic value) { + upgradeDto(value, "WorkflowShareResponseDto"); + if (value is Map) { + final json = value.cast(); + + return WorkflowShareResponseDto( + description: mapValueOfType(json, r'description'), + name: mapValueOfType(json, r'name'), + steps: WorkflowShareStepDto.listFromJson(json[r'steps']), + trigger: WorkflowTrigger.fromJson(json[r'trigger'])!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = WorkflowShareResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = WorkflowShareResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of WorkflowShareResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = WorkflowShareResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'description', + 'name', + 'steps', + 'trigger', + }; +} + diff --git a/mobile/openapi/lib/model/workflow_share_step_dto.dart b/mobile/openapi/lib/model/workflow_share_step_dto.dart new file mode 100644 index 0000000000..eeb6ba4e51 --- /dev/null +++ b/mobile/openapi/lib/model/workflow_share_step_dto.dart @@ -0,0 +1,130 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class WorkflowShareStepDto { + /// Returns a new [WorkflowShareStepDto] instance. + WorkflowShareStepDto({ + this.config = const {}, + this.enabled = const Optional.absent(), + required this.method, + }); + + /// Step configuration + Map? config; + + /// Step is enabled + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + Optional enabled; + + /// Step plugin method + String method; + + @override + bool operator ==(Object other) => identical(this, other) || other is WorkflowShareStepDto && + _deepEquality.equals(other.config, config) && + other.enabled == enabled && + other.method == method; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (config == null ? 0 : config!.hashCode) + + (enabled == null ? 0 : enabled!.hashCode) + + (method.hashCode); + + @override + String toString() => 'WorkflowShareStepDto[config=$config, enabled=$enabled, method=$method]'; + + Map toJson() { + final json = {}; + if (this.config != null) { + json[r'config'] = this.config; + } else { + json[r'config'] = null; + } + if (this.enabled.isPresent) { + final value = this.enabled.value; + json[r'enabled'] = value; + } + json[r'method'] = this.method; + return json; + } + + /// Returns a new [WorkflowShareStepDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static WorkflowShareStepDto? fromJson(dynamic value) { + upgradeDto(value, "WorkflowShareStepDto"); + if (value is Map) { + final json = value.cast(); + + return WorkflowShareStepDto( + config: mapCastOfType(json, r'config'), + enabled: json.containsKey(r'enabled') ? Optional.present(mapValueOfType(json, r'enabled')) : const Optional.absent(), + method: mapValueOfType(json, r'method')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = WorkflowShareStepDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = WorkflowShareStepDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of WorkflowShareStepDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = WorkflowShareStepDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'config', + 'method', + }; +} + diff --git a/mobile/openapi/lib/model/workflow_step_dto.dart b/mobile/openapi/lib/model/workflow_step_dto.dart new file mode 100644 index 0000000000..c01e8e6b44 --- /dev/null +++ b/mobile/openapi/lib/model/workflow_step_dto.dart @@ -0,0 +1,130 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class WorkflowStepDto { + /// Returns a new [WorkflowStepDto] instance. + WorkflowStepDto({ + this.config = const {}, + this.enabled = const Optional.absent(), + required this.method, + }); + + /// Step configuration + Map? config; + + /// Step is enabled + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + Optional enabled; + + /// Step plugin method + String method; + + @override + bool operator ==(Object other) => identical(this, other) || other is WorkflowStepDto && + _deepEquality.equals(other.config, config) && + other.enabled == enabled && + other.method == method; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (config == null ? 0 : config!.hashCode) + + (enabled == null ? 0 : enabled!.hashCode) + + (method.hashCode); + + @override + String toString() => 'WorkflowStepDto[config=$config, enabled=$enabled, method=$method]'; + + Map toJson() { + final json = {}; + if (this.config != null) { + json[r'config'] = this.config; + } else { + json[r'config'] = null; + } + if (this.enabled.isPresent) { + final value = this.enabled.value; + json[r'enabled'] = value; + } + json[r'method'] = this.method; + return json; + } + + /// Returns a new [WorkflowStepDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static WorkflowStepDto? fromJson(dynamic value) { + upgradeDto(value, "WorkflowStepDto"); + if (value is Map) { + final json = value.cast(); + + return WorkflowStepDto( + config: mapCastOfType(json, r'config'), + enabled: json.containsKey(r'enabled') ? Optional.present(mapValueOfType(json, r'enabled')) : const Optional.absent(), + method: mapValueOfType(json, r'method')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = WorkflowStepDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = WorkflowStepDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of WorkflowStepDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = WorkflowStepDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'config', + 'method', + }; +} + diff --git a/mobile/openapi/lib/model/workflow_trigger.dart b/mobile/openapi/lib/model/workflow_trigger.dart new file mode 100644 index 0000000000..b56d1b0dba --- /dev/null +++ b/mobile/openapi/lib/model/workflow_trigger.dart @@ -0,0 +1,88 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +/// Plugin trigger type +class WorkflowTrigger { + /// Instantiate a new enum with the provided [value]. + const WorkflowTrigger._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const assetCreate = WorkflowTrigger._(r'AssetCreate'); + static const assetMetadataExtraction = WorkflowTrigger._(r'AssetMetadataExtraction'); + static const personRecognized = WorkflowTrigger._(r'PersonRecognized'); + + /// List of all possible values in this [enum][WorkflowTrigger]. + static const values = [ + assetCreate, + assetMetadataExtraction, + personRecognized, + ]; + + static WorkflowTrigger? fromJson(dynamic value) => WorkflowTriggerTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = WorkflowTrigger.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [WorkflowTrigger] to String, +/// and [decode] dynamic data back to [WorkflowTrigger]. +class WorkflowTriggerTypeTransformer { + factory WorkflowTriggerTypeTransformer() => _instance ??= const WorkflowTriggerTypeTransformer._(); + + const WorkflowTriggerTypeTransformer._(); + + String encode(WorkflowTrigger data) => data.value; + + /// Decodes a [dynamic value][data] to a WorkflowTrigger. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + WorkflowTrigger? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'AssetCreate': return WorkflowTrigger.assetCreate; + case r'AssetMetadataExtraction': return WorkflowTrigger.assetMetadataExtraction; + case r'PersonRecognized': return WorkflowTrigger.personRecognized; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [WorkflowTriggerTypeTransformer] instance. + static WorkflowTriggerTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/workflow_trigger_response_dto.dart b/mobile/openapi/lib/model/workflow_trigger_response_dto.dart new file mode 100644 index 0000000000..6e24e1559a --- /dev/null +++ b/mobile/openapi/lib/model/workflow_trigger_response_dto.dart @@ -0,0 +1,108 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class WorkflowTriggerResponseDto { + /// Returns a new [WorkflowTriggerResponseDto] instance. + WorkflowTriggerResponseDto({ + required this.trigger, + this.types = const [], + }); + + WorkflowTrigger trigger; + + /// Workflow types + List types; + + @override + bool operator ==(Object other) => identical(this, other) || other is WorkflowTriggerResponseDto && + other.trigger == trigger && + _deepEquality.equals(other.types, types); + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (trigger.hashCode) + + (types.hashCode); + + @override + String toString() => 'WorkflowTriggerResponseDto[trigger=$trigger, types=$types]'; + + Map toJson() { + final json = {}; + json[r'trigger'] = this.trigger; + json[r'types'] = this.types; + return json; + } + + /// Returns a new [WorkflowTriggerResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static WorkflowTriggerResponseDto? fromJson(dynamic value) { + upgradeDto(value, "WorkflowTriggerResponseDto"); + if (value is Map) { + final json = value.cast(); + + return WorkflowTriggerResponseDto( + trigger: WorkflowTrigger.fromJson(json[r'trigger'])!, + types: WorkflowType.listFromJson(json[r'types']), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = WorkflowTriggerResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = WorkflowTriggerResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of WorkflowTriggerResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = WorkflowTriggerResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'trigger', + 'types', + }; +} + diff --git a/mobile/openapi/lib/model/workflow_type.dart b/mobile/openapi/lib/model/workflow_type.dart new file mode 100644 index 0000000000..c18b07e9fb --- /dev/null +++ b/mobile/openapi/lib/model/workflow_type.dart @@ -0,0 +1,85 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +/// Workflow type +class WorkflowType { + /// Instantiate a new enum with the provided [value]. + const WorkflowType._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const assetV1 = WorkflowType._(r'AssetV1'); + static const assetPersonV1 = WorkflowType._(r'AssetPersonV1'); + + /// List of all possible values in this [enum][WorkflowType]. + static const values = [ + assetV1, + assetPersonV1, + ]; + + static WorkflowType? fromJson(dynamic value) => WorkflowTypeTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = WorkflowType.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [WorkflowType] to String, +/// and [decode] dynamic data back to [WorkflowType]. +class WorkflowTypeTypeTransformer { + factory WorkflowTypeTypeTransformer() => _instance ??= const WorkflowTypeTypeTransformer._(); + + const WorkflowTypeTypeTransformer._(); + + String encode(WorkflowType data) => data.value; + + /// Decodes a [dynamic value][data] to a WorkflowType. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + WorkflowType? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'AssetV1': return WorkflowType.assetV1; + case r'AssetPersonV1': return WorkflowType.assetPersonV1; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [WorkflowTypeTypeTransformer] instance. + static WorkflowTypeTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/workflow_update_dto.dart b/mobile/openapi/lib/model/workflow_update_dto.dart index 9abb45ddd5..32759b6395 100644 --- a/mobile/openapi/lib/model/workflow_update_dto.dart +++ b/mobile/openapi/lib/model/workflow_update_dto.dart @@ -13,25 +13,15 @@ part of openapi.api; class WorkflowUpdateDto { /// Returns a new [WorkflowUpdateDto] instance. WorkflowUpdateDto({ - this.actions = const [], - this.description, - this.enabled, - this.filters = const [], - this.name, - this.triggerType, + this.description = const Optional.absent(), + this.enabled = const Optional.absent(), + this.name = const Optional.absent(), + this.steps = const Optional.present(const []), + this.trigger = const Optional.absent(), }); - /// Workflow actions - List actions; - /// Workflow description - /// - /// Please note: This property should have been non-nullable! Since the specification file - /// does not include a default value (using the "default:" property), however, the generated - /// source code must fall back to having a nullable type. - /// Consider adding a "default:" property in the specification file to hide this note. - /// - String? description; + Optional description; /// Workflow enabled /// @@ -40,19 +30,12 @@ class WorkflowUpdateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? enabled; - - /// Workflow filters - List filters; + Optional enabled; /// Workflow name - /// - /// Please note: This property should have been non-nullable! Since the specification file - /// does not include a default value (using the "default:" property), however, the generated - /// source code must fall back to having a nullable type. - /// Consider adding a "default:" property in the specification file to hide this note. - /// - String? name; + Optional name; + + Optional?> steps; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -60,53 +43,49 @@ class WorkflowUpdateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - PluginTriggerType? triggerType; + Optional trigger; @override bool operator ==(Object other) => identical(this, other) || other is WorkflowUpdateDto && - _deepEquality.equals(other.actions, actions) && other.description == description && other.enabled == enabled && - _deepEquality.equals(other.filters, filters) && other.name == name && - other.triggerType == triggerType; + _deepEquality.equals(other.steps, steps) && + other.trigger == trigger; @override int get hashCode => // ignore: unnecessary_parenthesis - (actions.hashCode) + (description == null ? 0 : description!.hashCode) + (enabled == null ? 0 : enabled!.hashCode) + - (filters.hashCode) + (name == null ? 0 : name!.hashCode) + - (triggerType == null ? 0 : triggerType!.hashCode); + (steps.hashCode) + + (trigger == null ? 0 : trigger!.hashCode); @override - String toString() => 'WorkflowUpdateDto[actions=$actions, description=$description, enabled=$enabled, filters=$filters, name=$name, triggerType=$triggerType]'; + String toString() => 'WorkflowUpdateDto[description=$description, enabled=$enabled, name=$name, steps=$steps, trigger=$trigger]'; Map toJson() { final json = {}; - json[r'actions'] = this.actions; - if (this.description != null) { - json[r'description'] = this.description; - } else { - // json[r'description'] = null; + if (this.description.isPresent) { + final value = this.description.value; + json[r'description'] = value; } - if (this.enabled != null) { - json[r'enabled'] = this.enabled; - } else { - // json[r'enabled'] = null; + if (this.enabled.isPresent) { + final value = this.enabled.value; + json[r'enabled'] = value; } - json[r'filters'] = this.filters; - if (this.name != null) { - json[r'name'] = this.name; - } else { - // json[r'name'] = null; + if (this.name.isPresent) { + final value = this.name.value; + json[r'name'] = value; } - if (this.triggerType != null) { - json[r'triggerType'] = this.triggerType; - } else { - // json[r'triggerType'] = null; + if (this.steps.isPresent) { + final value = this.steps.value; + json[r'steps'] = value; + } + if (this.trigger.isPresent) { + final value = this.trigger.value; + json[r'trigger'] = value; } return json; } @@ -120,12 +99,11 @@ class WorkflowUpdateDto { final json = value.cast(); return WorkflowUpdateDto( - actions: WorkflowActionItemDto.listFromJson(json[r'actions']), - description: mapValueOfType(json, r'description'), - enabled: mapValueOfType(json, r'enabled'), - filters: WorkflowFilterItemDto.listFromJson(json[r'filters']), - name: mapValueOfType(json, r'name'), - triggerType: PluginTriggerType.fromJson(json[r'triggerType']), + description: json.containsKey(r'description') ? Optional.present(mapValueOfType(json, r'description')) : const Optional.absent(), + enabled: json.containsKey(r'enabled') ? Optional.present(mapValueOfType(json, r'enabled')) : const Optional.absent(), + name: json.containsKey(r'name') ? Optional.present(mapValueOfType(json, r'name')) : const Optional.absent(), + steps: json.containsKey(r'steps') ? Optional.present(WorkflowStepDto.listFromJson(json[r'steps'])) : const Optional.absent(), + trigger: json.containsKey(r'trigger') ? Optional.present(WorkflowTrigger.fromJson(json[r'trigger'])) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/optional.dart b/mobile/openapi/lib/optional.dart new file mode 100644 index 0000000000..f260ec4a84 --- /dev/null +++ b/mobile/openapi/lib/optional.dart @@ -0,0 +1,119 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +/// Represents an optional value that can be either absent or present. +/// +/// This is used to distinguish between three states in PATCH operations: +/// - Absent: Field is not set (omitted from JSON) +/// - Present with null: Field is explicitly set to null +/// - Present with value: Field has a value +/// +/// Example usage: +/// ```dart +/// // Field absent - not sent in request +/// final patch1 = Model(); +/// +/// // Field explicitly null - sends {"field": null} +/// final patch2 = Model(field: const Optional.present(null)); +/// +/// // Field has value - sends {"field": "value"} +/// final patch3 = Model(field: const Optional.present('value')); +/// ``` +abstract class Optional { + const Optional(); + + /// Creates an Optional with an absent value (not set). + const factory Optional.absent() = Absent; + + /// Creates an Optional with a present value (can be null). + const factory Optional.present(T value) = Present; + + /// Returns true if this Optional has a value (even if that value is null). + bool get isPresent; + + /// Returns true if this Optional does not have a value. + bool get isEmpty => !isPresent; + + /// Returns the value if present, throws if absent. + T get value; + + /// Returns the value if present, otherwise returns [defaultValue]. + T orElse(T defaultValue); + + /// Returns the value if present, otherwise returns the result of calling [defaultValue]. + T orElseGet(T Function() defaultValue); + + /// Maps the value if present using [transform], otherwise returns an absent Optional. + Optional map(R Function(T value) transform); +} + +/// Represents an absent Optional value. +class Absent extends Optional { + const Absent(); + + @override + bool get isPresent => false; + + @override + T get value => throw StateError('No value present'); + + @override + T orElse(T defaultValue) => defaultValue; + + @override + T orElseGet(T Function() defaultValue) => defaultValue(); + + @override + Optional map(R Function(T value) transform) => const Absent(); + + @override + bool operator ==(Object other) => other is Absent; + + @override + int get hashCode => 0; + + @override + String toString() => 'Optional.absent()'; +} + +/// Represents a present Optional value. +class Present extends Optional { + const Present(this._value); + + final T _value; + + @override + bool get isPresent => true; + + @override + T get value => _value; + + @override + T orElse(T defaultValue) => _value; + + @override + T orElseGet(T Function() defaultValue) => _value; + + @override + Optional map(R Function(T value) transform) => Optional.present(transform(_value)); + + @override + bool operator ==(Object other) => + identical(this, other) || + (other is Present && _value == other._value); + + @override + int get hashCode => _value.hashCode; + + @override + String toString() => 'Optional.present($_value)'; +} diff --git a/mobile/packages/ui/lib/immich_ui.dart b/mobile/packages/ui/lib/immich_ui.dart index c9e510a162..6158b4f0e1 100644 --- a/mobile/packages/ui/lib/immich_ui.dart +++ b/mobile/packages/ui/lib/immich_ui.dart @@ -5,6 +5,7 @@ export 'src/components/icon_button.dart'; export 'src/components/password_input.dart'; export 'src/components/text_button.dart'; export 'src/components/text_input.dart'; +export 'src/components/url_input.dart'; export 'src/constants.dart'; export 'src/theme.dart'; export 'src/translation.dart'; diff --git a/mobile/packages/ui/lib/src/components/form.dart b/mobile/packages/ui/lib/src/components/form.dart index 9e8c161806..31e9d01980 100644 --- a/mobile/packages/ui/lib/src/components/form.dart +++ b/mobile/packages/ui/lib/src/components/form.dart @@ -4,95 +4,98 @@ import 'package:flutter/material.dart'; import 'package:immich_ui/immich_ui.dart'; import 'package:immich_ui/src/internal.dart'; -class ImmichForm extends StatefulWidget { - final String? submitText; - final IconData? submitIcon; - final FutureOr Function()? onSubmit; - final Widget child; +class ImmichFormController extends ChangeNotifier { + ImmichFormController({this.onSubmit}); - const ImmichForm({ - super.key, - this.submitText, - this.submitIcon, - required this.onSubmit, - required this.child, - }); + FutureOr Function()? onSubmit; + final formKey = GlobalKey(); + + bool _isDisposed = false; + bool _isLoading = false; + bool get isLoading => _isLoading; @override - State createState() => ImmichFormState(); - - static ImmichFormState of(BuildContext context) { - final scope = context.dependOnInheritedWidgetOfExactType<_ImmichFormScope>(); - if (scope == null) { - throw FlutterError( - 'ImmichForm.of() called with a context that does not contain an ImmichForm.\n' - 'No ImmichForm ancestor could be found starting from the context that was passed to ' - 'ImmichForm.of(). This usually happens when the context provided is ' - 'from a widget above the ImmichForm.\n' - 'The context used was:\n' - '$context', - ); - } - return scope._formState; + void dispose() { + _isDisposed = true; + super.dispose(); } -} -class ImmichFormState extends State { - final _formKey = GlobalKey(); - bool _isLoading = false; - - FutureOr submit() async { - final isValid = _formKey.currentState?.validate() ?? false; - if (!isValid) { + Future submit() async { + if (_isLoading) { + return; + } + if (!(formKey.currentState?.validate() ?? false)) { return; } - setState(() { - _isLoading = true; - }); - + _isLoading = true; + notifyListeners(); try { - await widget.onSubmit?.call(); + await onSubmit?.call(); } finally { - if (mounted) { - setState(() { - _isLoading = false; - }); + _isLoading = false; + if (!_isDisposed) { + notifyListeners(); } } } +} + +class ImmichForm extends StatefulWidget { + final FutureOr Function()? onSubmit; + final Widget Function(BuildContext context, ImmichFormController form) builder; + final String? submitText; + final IconData? submitIcon; + + const ImmichForm({super.key, this.onSubmit, this.submitText, this.submitIcon, required this.builder}); + + @override + State createState() => _ImmichFormState(); +} + +class _ImmichFormState extends State { + late final ImmichFormController _controller; + + @override + void initState() { + super.initState(); + _controller = ImmichFormController(onSubmit: widget.onSubmit); + } + + @override + void didUpdateWidget(ImmichForm oldWidget) { + super.didUpdateWidget(oldWidget); + _controller.onSubmit = widget.onSubmit; + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } @override Widget build(BuildContext context) { final submitText = widget.submitText ?? context.translations.submit; - return _ImmichFormScope( - formState: this, - child: Form( - key: _formKey, - child: Column( - spacing: ImmichSpacing.md, - children: [ - widget.child, - ImmichTextButton( + return Form( + key: _controller.formKey, + child: Column( + spacing: ImmichSpacing.md, + children: [ + widget.builder(context, _controller), + ListenableBuilder( + listenable: _controller, + builder: (context, _) => ImmichTextButton( labelText: submitText, icon: widget.submitIcon, variant: ImmichVariant.filled, - loading: _isLoading, - onPressed: submit, - disabled: widget.onSubmit == null, + loading: _controller.isLoading, + onPressed: _controller.submit, + disabled: _controller.onSubmit == null, ), - ], - ), + ), + ], ), ); } } - -class _ImmichFormScope extends InheritedWidget { - const _ImmichFormScope({required super.child, required ImmichFormState formState}) : _formState = formState; - - final ImmichFormState _formState; - - @override - bool updateShouldNotify(_ImmichFormScope oldWidget) => oldWidget._formState != _formState; -} diff --git a/mobile/packages/ui/lib/src/components/password_input.dart b/mobile/packages/ui/lib/src/components/password_input.dart index bd5a149354..70808472df 100644 --- a/mobile/packages/ui/lib/src/components/password_input.dart +++ b/mobile/packages/ui/lib/src/components/password_input.dart @@ -8,7 +8,7 @@ class ImmichPasswordInput extends StatefulWidget { final TextEditingController? controller; final FocusNode? focusNode; final String? Function(String?)? validator; - final void Function(BuildContext, String)? onSubmit; + final void Function(String value)? onSubmit; final TextInputAction? keyboardAction; const ImmichPasswordInput({ diff --git a/mobile/packages/ui/lib/src/components/text_input.dart b/mobile/packages/ui/lib/src/components/text_input.dart index 1b3fb91f51..71d3248ac1 100644 --- a/mobile/packages/ui/lib/src/components/text_input.dart +++ b/mobile/packages/ui/lib/src/components/text_input.dart @@ -1,33 +1,46 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; class ImmichTextInput extends StatefulWidget { - final String label; + final String? label; final String? hintText; final TextEditingController? controller; final FocusNode? focusNode; final String? Function(String?)? validator; - final void Function(BuildContext, String)? onSubmit; + final void Function(String value)? onSubmit; final TextInputType keyboardType; final TextInputAction? keyboardAction; final List? autofillHints; final Widget? suffixIcon; final bool obscureText; - final bool autoCorrect; + final bool autocorrect; + final SmartDashesType? smartDashesType; + final SmartQuotesType? smartQuotesType; + final List? inputFormatters; + final bool enabled; + final bool autofocus; + final AutovalidateMode? autovalidateMode; const ImmichTextInput({ super.key, this.controller, this.focusNode, - required this.label, + this.label, this.hintText, this.validator, this.onSubmit, - this.keyboardType = TextInputType.text, + this.keyboardType = .text, this.keyboardAction, this.autofillHints, this.suffixIcon, this.obscureText = false, - this.autoCorrect = true, + this.autocorrect = true, + this.smartDashesType, + this.smartQuotesType, + this.inputFormatters, + this.enabled = true, + this.autofocus = false, + this.autovalidateMode, }); @override @@ -36,7 +49,6 @@ class ImmichTextInput extends StatefulWidget { class _ImmichTextInputState extends State { late final FocusNode _focusNode; - String? _error; @override void initState() { @@ -52,40 +64,26 @@ class _ImmichTextInputState extends State { super.dispose(); } - String? _validateInput(String? value) { - setState(() { - _error = widget.validator?.call(value); - }); - return null; - } - - bool get _hasError => _error != null && _error!.isNotEmpty; - @override Widget build(BuildContext context) { - final themeData = Theme.of(context); - return TextFormField( controller: widget.controller, focusNode: _focusNode, - decoration: InputDecoration( - hintText: widget.hintText, - labelText: widget.label, - labelStyle: themeData.inputDecorationTheme.labelStyle?.copyWith( - color: _hasError ? themeData.colorScheme.error : null, - ), - errorText: _error, - suffixIcon: widget.suffixIcon, - ), + enabled: widget.enabled, + autofocus: widget.autofocus, + autovalidateMode: widget.autovalidateMode, + decoration: InputDecoration(hintText: widget.hintText, labelText: widget.label, suffixIcon: widget.suffixIcon), obscureText: widget.obscureText, - validator: _validateInput, - keyboardType: widget.keyboardType, + validator: widget.validator, textInputAction: widget.keyboardAction, - autocorrect: widget.autoCorrect, - autofillHints: widget.autofillHints, - onTap: () => setState(() => _error = null), onTapOutside: (_) => _focusNode.unfocus(), - onFieldSubmitted: (value) => widget.onSubmit?.call(context, value), + onFieldSubmitted: (value) => widget.onSubmit?.call(value), + keyboardType: widget.keyboardType, + autofillHints: widget.autofillHints, + autocorrect: widget.autocorrect, + smartDashesType: widget.smartDashesType, + smartQuotesType: widget.smartQuotesType, + inputFormatters: widget.inputFormatters, ); } } diff --git a/mobile/packages/ui/lib/src/components/url_input.dart b/mobile/packages/ui/lib/src/components/url_input.dart new file mode 100644 index 0000000000..b23136a122 --- /dev/null +++ b/mobile/packages/ui/lib/src/components/url_input.dart @@ -0,0 +1,30 @@ +import 'package:flutter/services.dart'; +import 'package:immich_ui/src/components/text_input.dart'; + +class ImmichURLInput extends ImmichTextInput { + ImmichURLInput({ + super.key, + super.controller, + super.focusNode, + super.label, + super.hintText, + super.validator, + super.onSubmit, + super.keyboardAction, + super.suffixIcon, + super.enabled, + super.autofocus, + super.autovalidateMode, + }) : super( + keyboardType: .url, + autofillHints: const [AutofillHints.url], + autocorrect: false, + smartDashesType: .disabled, + smartQuotesType: .disabled, + inputFormatters: _formatters, + ); + + static final List _formatters = List.unmodifiable([ + FilteringTextInputFormatter.deny(RegExp(r'\s')), + ]); +} diff --git a/mobile/packages/ui/lib/src/previews.dart b/mobile/packages/ui/lib/src/previews.dart new file mode 100644 index 0000000000..076bc253c9 --- /dev/null +++ b/mobile/packages/ui/lib/src/previews.dart @@ -0,0 +1,60 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widget_previews.dart'; +import 'package:immich_ui/src/theme.dart'; + +const ColorScheme _lightColorScheme = ColorScheme.light( + primary: Color(0xFF4250AF), + onPrimary: Colors.white, + primaryContainer: Color(0xFFD4D6F0), + onPrimaryContainer: Color(0xFF181E44), + secondary: Color(0xFF737373), + onSecondary: Colors.white, + error: Color(0xFFE53E3E), + onError: Colors.white, + surface: Color(0xFFFAFAFA), + onSurface: Color(0xFF1A1C1E), + surfaceContainerHighest: Color(0xFFE3E4E8), + outline: Color(0xFFD1D3D9), + outlineVariant: Color(0xFFD4D4D4), +); + +const ColorScheme _darkColorScheme = ColorScheme.dark( + primary: Color(0xFFACCBFA), + onPrimary: Color(0xFF0F1433), + primaryContainer: Color(0xFF616D94), + onPrimaryContainer: Color(0xFFD4D6F0), + secondary: Color(0xFFC4C6D0), + onSecondary: Color(0xFF2E3042), + error: Color(0xFFE88080), + onError: Color(0xFF0F1433), + surface: Color(0xFF0A0A0A), + onSurface: Color(0xFFE3E3E6), + surfaceContainerHighest: Color(0xFF262626), + outline: Color(0xFF8E9099), + outlineVariant: Color(0xFF43464F), +); + +PreviewThemeData immichPreviewTheme() => PreviewThemeData( + materialLight: ThemeData(colorScheme: _lightColorScheme, useMaterial3: true), + materialDark: ThemeData(colorScheme: _darkColorScheme, useMaterial3: true), + ); + +Widget immichPreviewWrapper(Widget child) { + return Builder( + builder: (context) => ImmichThemeProvider( + colorScheme: Theme.of(context).colorScheme, + child: Scaffold( + backgroundColor: Theme.of(context).colorScheme.surface, + body: Padding( + padding: const EdgeInsets.all(16), + child: Align(alignment: Alignment.topLeft, child: child), + ), + ), + ), + ); +} + +final class ImmichPreview extends Preview { + const ImmichPreview({super.name, super.group, super.size, super.textScaleFactor}) + : super(theme: immichPreviewTheme, wrapper: immichPreviewWrapper); +} diff --git a/mobile/packages/ui/lib/src/previews/close_button.dart b/mobile/packages/ui/lib/src/previews/close_button.dart new file mode 100644 index 0000000000..678d6bed18 --- /dev/null +++ b/mobile/packages/ui/lib/src/previews/close_button.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart'; +import 'package:immich_ui/src/components/close_button.dart'; +import 'package:immich_ui/src/previews.dart'; +import 'package:immich_ui/src/types.dart'; + +void _previewNoop() {} + +@ImmichPreview(group: 'CloseButton', name: 'Variants') +Widget previewCloseButtonVariants() => const Wrap( + spacing: 12, + runSpacing: 12, + children: [ + ImmichCloseButton(onPressed: _previewNoop), + ImmichCloseButton(onPressed: _previewNoop, variant: ImmichVariant.filled), + ], + ); + +@ImmichPreview(group: 'CloseButton', name: 'Colors') +Widget previewCloseButtonColors() => const Wrap( + spacing: 12, + runSpacing: 12, + children: [ + ImmichCloseButton(onPressed: _previewNoop), + ImmichCloseButton(onPressed: _previewNoop, color: ImmichColor.secondary), + ], + ); diff --git a/mobile/packages/ui/lib/src/previews/form.dart b/mobile/packages/ui/lib/src/previews/form.dart new file mode 100644 index 0000000000..9e488cfd68 --- /dev/null +++ b/mobile/packages/ui/lib/src/previews/form.dart @@ -0,0 +1,72 @@ +import 'package:flutter/material.dart'; +import 'package:immich_ui/src/components/form.dart'; +import 'package:immich_ui/src/components/password_input.dart'; +import 'package:immich_ui/src/components/text_input.dart'; +import 'package:immich_ui/src/constants.dart'; +import 'package:immich_ui/src/previews.dart'; + +@ImmichPreview(group: 'Form', name: 'Login Form') +Widget previewFormLogin() => const _PreviewLoginForm(); + +class _PreviewLoginForm extends StatefulWidget { + const _PreviewLoginForm(); + + @override + State<_PreviewLoginForm> createState() => _PreviewLoginFormState(); +} + +class _PreviewLoginFormState extends State<_PreviewLoginForm> { + final _emailController = TextEditingController(); + final _passwordController = TextEditingController(); + String _result = ''; + + @override + void dispose() { + _emailController.dispose(); + _passwordController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ImmichForm( + submitText: 'Login', + submitIcon: Icons.login, + onSubmit: () async { + await Future.delayed(const Duration(seconds: 1)); + if (!mounted) { + return; + } + setState(() { + _result = 'Form submitted!'; + }); + }, + builder: (context, form) => Column( + spacing: ImmichSpacing.sm, + children: [ + ImmichTextInput( + label: 'Email', + controller: _emailController, + keyboardType: TextInputType.emailAddress, + validator: (value) => value?.isEmpty ?? true ? 'Required' : null, + ), + ImmichPasswordInput( + label: 'Password', + controller: _passwordController, + validator: (value) => value?.isEmpty ?? true ? 'Required' : null, + onSubmit: (_) => form.submit(), + ), + ], + ), + ), + if (_result.isNotEmpty) ...[ + const SizedBox(height: 16), + Text(_result, style: const TextStyle(color: Colors.green)), + ], + ], + ); + } +} diff --git a/mobile/packages/ui/lib/src/previews/formatted_text.dart b/mobile/packages/ui/lib/src/previews/formatted_text.dart new file mode 100644 index 0000000000..9ef3b4b851 --- /dev/null +++ b/mobile/packages/ui/lib/src/previews/formatted_text.dart @@ -0,0 +1,50 @@ +import 'package:flutter/material.dart'; +import 'package:immich_ui/src/components/formatted_text.dart'; +import 'package:immich_ui/src/previews.dart'; + +@ImmichPreview(group: 'FormattedText', name: 'Bold') +Widget previewFormattedTextBold() => const ImmichFormattedText('This is bold text.'); + +@ImmichPreview(group: 'FormattedText', name: 'Links') +Widget previewFormattedTextLinks() => const _PreviewFormattedTextLinks(); + +@ImmichPreview(group: 'FormattedText', name: 'Mixed Content') +Widget previewFormattedTextMixed() => const _PreviewFormattedTextMixed(); + +class _PreviewFormattedTextLinks extends StatelessWidget { + const _PreviewFormattedTextLinks(); + + @override + Widget build(BuildContext context) { + return ImmichFormattedText( + 'Read the documentation or visit GitHub.', + spanBuilder: (tag) => FormattedSpan( + onTap: switch (tag) { + 'docs-link' => + () => ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Docs link clicked!'))), + 'github-link' => + () => ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('GitHub link clicked!'))), + _ => null, + }, + ), + ); + } +} + +class _PreviewFormattedTextMixed extends StatelessWidget { + const _PreviewFormattedTextMixed(); + + @override + Widget build(BuildContext context) { + return ImmichFormattedText( + 'You can use bold text and links together.', + spanBuilder: (tag) => switch (tag) { + 'b' => const FormattedSpan(style: TextStyle(fontWeight: FontWeight.bold)), + _ => FormattedSpan( + onTap: () => + ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Link clicked!'))), + ), + }, + ); + } +} diff --git a/mobile/packages/ui/lib/src/previews/icon_button.dart b/mobile/packages/ui/lib/src/previews/icon_button.dart new file mode 100644 index 0000000000..6a0196bc81 --- /dev/null +++ b/mobile/packages/ui/lib/src/previews/icon_button.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; +import 'package:immich_ui/src/components/icon_button.dart'; +import 'package:immich_ui/src/previews.dart'; +import 'package:immich_ui/src/types.dart'; + +void _previewNoop() {} + +@ImmichPreview(group: 'IconButton', name: 'Variants') +Widget previewIconButtonVariants() => const Wrap( + spacing: 12, + runSpacing: 12, + children: [ + ImmichIconButton(icon: Icons.add, onPressed: _previewNoop), + ImmichIconButton(icon: Icons.edit, onPressed: _previewNoop, variant: ImmichVariant.ghost), + ], + ); + +@ImmichPreview(group: 'IconButton', name: 'Colors') +Widget previewIconButtonColors() => const Wrap( + spacing: 12, + runSpacing: 12, + children: [ + ImmichIconButton(icon: Icons.favorite, onPressed: _previewNoop), + ImmichIconButton(icon: Icons.delete, onPressed: _previewNoop, color: ImmichColor.secondary), + ], + ); + +@ImmichPreview(group: 'IconButton', name: 'Disabled') +Widget previewIconButtonDisabled() => const Wrap( + spacing: 12, + runSpacing: 12, + children: [ + ImmichIconButton(icon: Icons.settings, onPressed: _previewNoop, disabled: true), + ImmichIconButton( + icon: Icons.settings, + onPressed: _previewNoop, + disabled: true, + variant: ImmichVariant.ghost, + ), + ], + ); diff --git a/mobile/packages/ui/lib/src/previews/password_input.dart b/mobile/packages/ui/lib/src/previews/password_input.dart new file mode 100644 index 0000000000..72bd9cbfc5 --- /dev/null +++ b/mobile/packages/ui/lib/src/previews/password_input.dart @@ -0,0 +1,18 @@ +import 'package:flutter/material.dart'; +import 'package:immich_ui/src/components/password_input.dart'; +import 'package:immich_ui/src/previews.dart'; + +@ImmichPreview(group: 'PasswordInput', name: 'With Validator') +Widget previewPasswordInput() => ImmichPasswordInput( + label: 'Password', + hintText: 'Enter your password', + validator: (value) { + if (value == null || value.isEmpty) { + return 'Password is required'; + } + if (value.length < 8) { + return 'Password must be at least 8 characters'; + } + return null; + }, + ); diff --git a/mobile/packages/ui/lib/src/previews/text_button.dart b/mobile/packages/ui/lib/src/previews/text_button.dart new file mode 100644 index 0000000000..46c689bbae --- /dev/null +++ b/mobile/packages/ui/lib/src/previews/text_button.dart @@ -0,0 +1,88 @@ +import 'package:flutter/material.dart'; +import 'package:immich_ui/src/components/text_button.dart'; +import 'package:immich_ui/src/previews.dart'; +import 'package:immich_ui/src/types.dart'; + +void _previewNoop() {} + +@ImmichPreview(group: 'TextButton', name: 'Variants') +Widget previewTextButtonVariants() => const Wrap( + spacing: 12, + runSpacing: 12, + children: [ + ImmichTextButton(onPressed: _previewNoop, labelText: 'Filled', expanded: false), + ImmichTextButton(onPressed: _previewNoop, labelText: 'Ghost', variant: ImmichVariant.ghost, expanded: false), + ], +); + +@ImmichPreview(group: 'TextButton', name: 'Colors') +Widget previewTextButtonColors() => const Wrap( + spacing: 12, + runSpacing: 12, + children: [ + ImmichTextButton(onPressed: _previewNoop, labelText: 'Primary', expanded: false), + ImmichTextButton(onPressed: _previewNoop, labelText: 'Secondary', color: ImmichColor.secondary, expanded: false), + ], +); + +@ImmichPreview(group: 'TextButton', name: 'With Icons') +Widget previewTextButtonWithIcons() => const Wrap( + spacing: 12, + runSpacing: 12, + children: [ + ImmichTextButton(onPressed: _previewNoop, labelText: 'With Icon', icon: Icons.add, expanded: false), + ImmichTextButton( + onPressed: _previewNoop, + labelText: 'Download', + icon: Icons.download, + variant: ImmichVariant.ghost, + expanded: false, + ), + ], +); + +@ImmichPreview(group: 'TextButton', name: 'Loading') +Widget previewTextButtonLoading() => const _PreviewLoadingDemo(); + +@ImmichPreview(group: 'TextButton', name: 'Disabled') +Widget previewTextButtonDisabled() => const Wrap( + spacing: 12, + runSpacing: 12, + children: [ + ImmichTextButton(onPressed: _previewNoop, labelText: 'Disabled', disabled: true, expanded: false), + ImmichTextButton( + onPressed: _previewNoop, + labelText: 'Disabled Ghost', + variant: ImmichVariant.ghost, + disabled: true, + expanded: false, + ), + ], +); + +class _PreviewLoadingDemo extends StatefulWidget { + const _PreviewLoadingDemo(); + + @override + State<_PreviewLoadingDemo> createState() => _PreviewLoadingDemoState(); +} + +class _PreviewLoadingDemoState extends State<_PreviewLoadingDemo> { + bool _isLoading = false; + + @override + Widget build(BuildContext context) { + return ImmichTextButton( + onPressed: () async { + setState(() => _isLoading = true); + await Future.delayed(const Duration(seconds: 2)); + if (mounted) { + setState(() => _isLoading = false); + } + }, + labelText: _isLoading ? 'Loading...' : 'Click Me', + loading: _isLoading, + expanded: false, + ); + } +} diff --git a/mobile/packages/ui/lib/src/previews/text_input.dart b/mobile/packages/ui/lib/src/previews/text_input.dart new file mode 100644 index 0000000000..fab58c8cc4 --- /dev/null +++ b/mobile/packages/ui/lib/src/previews/text_input.dart @@ -0,0 +1,71 @@ +import 'package:flutter/material.dart'; +import 'package:immich_ui/src/components/text_input.dart'; +import 'package:immich_ui/src/previews.dart'; + +@ImmichPreview(group: 'TextInput', name: 'Basic') +Widget previewTextInputBasic() => const _PreviewTextInputBasic(); + +@ImmichPreview(group: 'TextInput', name: 'With Validator') +Widget previewTextInputValidator() => const _PreviewTextInputValidator(); + +class _PreviewTextInputBasic extends StatefulWidget { + const _PreviewTextInputBasic(); + + @override + State<_PreviewTextInputBasic> createState() => _PreviewTextInputBasicState(); +} + +class _PreviewTextInputBasicState extends State<_PreviewTextInputBasic> { + final _controller = TextEditingController(); + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return ImmichTextInput( + label: 'Email', + hintText: 'Enter your email', + controller: _controller, + keyboardType: TextInputType.emailAddress, + ); + } +} + +class _PreviewTextInputValidator extends StatefulWidget { + const _PreviewTextInputValidator(); + + @override + State<_PreviewTextInputValidator> createState() => _PreviewTextInputValidatorState(); +} + +class _PreviewTextInputValidatorState extends State<_PreviewTextInputValidator> { + final _controller = TextEditingController(); + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return ImmichTextInput( + label: 'Username', + controller: _controller, + autovalidateMode: AutovalidateMode.onUserInteraction, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Username is required'; + } + if (value.length < 3) { + return 'Username must be at least 3 characters'; + } + return null; + }, + ); + } +} diff --git a/mobile/packages/ui/lib/src/previews/url_input.dart b/mobile/packages/ui/lib/src/previews/url_input.dart new file mode 100644 index 0000000000..6819ac5796 --- /dev/null +++ b/mobile/packages/ui/lib/src/previews/url_input.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; +import 'package:immich_ui/src/components/url_input.dart'; +import 'package:immich_ui/src/previews.dart'; + +@ImmichPreview(group: 'URLInput', name: 'Basic') +Widget previewUrlInput() => const _PreviewUrlInput(); + +class _PreviewUrlInput extends StatefulWidget { + const _PreviewUrlInput(); + + @override + State<_PreviewUrlInput> createState() => _PreviewUrlInputState(); +} + +class _PreviewUrlInputState extends State<_PreviewUrlInput> { + final _controller = TextEditingController(); + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return ImmichURLInput(label: 'Server URL', hintText: 'https://demo.immich.com', controller: _controller); + } +} diff --git a/mobile/packages/ui/lib/src/theme.dart b/mobile/packages/ui/lib/src/theme.dart index 387723b8ce..891b41ee91 100644 --- a/mobile/packages/ui/lib/src/theme.dart +++ b/mobile/packages/ui/lib/src/theme.dart @@ -15,23 +15,21 @@ class ImmichThemeProvider extends StatelessWidget { brightness: colorScheme.brightness, inputDecorationTheme: InputDecorationTheme( floatingLabelBehavior: FloatingLabelBehavior.always, - focusedBorder: OutlineInputBorder( - borderSide: BorderSide(color: colorScheme.primary), - borderRadius: const BorderRadius.all(Radius.circular(ImmichRadius.md)), - ), - enabledBorder: OutlineInputBorder( - borderSide: BorderSide(color: colorScheme.primary), - borderRadius: const BorderRadius.all(Radius.circular(ImmichRadius.md)), - ), - errorBorder: OutlineInputBorder( - borderSide: BorderSide(color: colorScheme.error), - borderRadius: const BorderRadius.all(Radius.circular(ImmichRadius.md)), - ), - focusedErrorBorder: OutlineInputBorder( - borderSide: BorderSide(color: colorScheme.error), - borderRadius: const BorderRadius.all(Radius.circular(ImmichRadius.md)), - ), - labelStyle: TextStyle(color: colorScheme.primary, fontWeight: FontWeight.w600), + border: WidgetStateInputBorder.resolveWith((states) { + final color = states.contains(WidgetState.error) + ? colorScheme.error + : states.contains(WidgetState.focused) + ? colorScheme.primary + : colorScheme.outline; + return OutlineInputBorder( + borderSide: BorderSide(color: color), + borderRadius: const BorderRadius.all(Radius.circular(ImmichRadius.md)), + ); + }), + labelStyle: WidgetStateTextStyle.resolveWith((states) { + final color = states.contains(WidgetState.error) ? colorScheme.error : colorScheme.primary; + return TextStyle(color: color, fontWeight: FontWeight.w600); + }), hintStyle: const TextStyle(fontSize: ImmichTextSize.body), errorStyle: TextStyle(color: colorScheme.error, fontWeight: FontWeight.w600), ), diff --git a/mobile/packages/ui/pubspec.lock b/mobile/packages/ui/pubspec.lock index 4ac863d0f7..9d11b49253 100644 --- a/mobile/packages/ui/pubspec.lock +++ b/mobile/packages/ui/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: async - sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 url: "https://pub.dev" source: hosted - version: "2.13.0" + version: "2.13.1" boolean_selector: dependency: transitive description: @@ -103,10 +103,10 @@ packages: dependency: transitive description: name: meta - sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.18.0" path: dependency: transitive description: @@ -124,10 +124,10 @@ packages: dependency: transitive description: name: source_span - sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" url: "https://pub.dev" source: hosted - version: "1.10.1" + version: "1.10.2" stack_trace: dependency: transitive description: @@ -164,10 +164,10 @@ packages: dependency: transitive description: name: test_api - sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" + sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" url: "https://pub.dev" source: hosted - version: "0.7.10" + version: "0.7.11" vector_math: dependency: transitive description: @@ -180,10 +180,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" + sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" url: "https://pub.dev" source: hosted - version: "15.0.2" + version: "15.2.0" sdks: - dart: ">=3.11.0 <4.0.0" + dart: ">=3.12.0 <4.0.0" flutter: ">=3.18.0-18.0.pre.54" diff --git a/mobile/packages/ui/pubspec.yaml b/mobile/packages/ui/pubspec.yaml index de50e0a429..1f44694ace 100644 --- a/mobile/packages/ui/pubspec.yaml +++ b/mobile/packages/ui/pubspec.yaml @@ -2,7 +2,7 @@ name: immich_ui publish_to: none environment: - sdk: '>=3.11.0 <4.0.0' + sdk: '>=3.12.0 <4.0.0' dependencies: flutter: diff --git a/mobile/packages/ui/showcase/.gitignore b/mobile/packages/ui/showcase/.gitignore deleted file mode 100644 index b285cd608b..0000000000 --- a/mobile/packages/ui/showcase/.gitignore +++ /dev/null @@ -1,11 +0,0 @@ -# Build artifacts -build/ - -# Test cache and generated files -.dart_tool/ -.packages -.flutter-plugins -.flutter-plugins-dependencies - -# IDE-specific files -.vscode/ \ No newline at end of file diff --git a/mobile/packages/ui/showcase/.metadata b/mobile/packages/ui/showcase/.metadata deleted file mode 100644 index b95fa4d74e..0000000000 --- a/mobile/packages/ui/showcase/.metadata +++ /dev/null @@ -1,30 +0,0 @@ -# This file tracks properties of this Flutter project. -# Used by Flutter tool to assess capabilities and perform upgrades etc. -# -# This file should be version controlled and should not be manually edited. - -version: - revision: "adc901062556672b4138e18a4dc62a4be8f4b3c2" - channel: "stable" - -project_type: app - -# Tracks metadata for the flutter migrate command -migration: - platforms: - - platform: root - create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 - base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 - - platform: web - create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 - base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 - - # User provided section - - # List of Local paths (relative to this file) that should be - # ignored by the migrate tool. - # - # Files that are not part of the templates will be ignored by default. - unmanaged_files: - - 'lib/main.dart' - - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/mobile/packages/ui/showcase/analysis_options.yaml b/mobile/packages/ui/showcase/analysis_options.yaml deleted file mode 100644 index f9b303465f..0000000000 --- a/mobile/packages/ui/showcase/analysis_options.yaml +++ /dev/null @@ -1 +0,0 @@ -include: package:flutter_lints/flutter.yaml diff --git a/mobile/packages/ui/showcase/assets/immich-text-dark.png b/mobile/packages/ui/showcase/assets/immich-text-dark.png deleted file mode 100644 index 215687af8f..0000000000 Binary files a/mobile/packages/ui/showcase/assets/immich-text-dark.png and /dev/null differ diff --git a/mobile/packages/ui/showcase/assets/immich-text-light.png b/mobile/packages/ui/showcase/assets/immich-text-light.png deleted file mode 100644 index 478158d39c..0000000000 Binary files a/mobile/packages/ui/showcase/assets/immich-text-light.png and /dev/null differ diff --git a/mobile/packages/ui/showcase/assets/immich_logo.png b/mobile/packages/ui/showcase/assets/immich_logo.png deleted file mode 100644 index 49fd3ae289..0000000000 Binary files a/mobile/packages/ui/showcase/assets/immich_logo.png and /dev/null differ diff --git a/mobile/packages/ui/showcase/assets/themes/github_dark.json b/mobile/packages/ui/showcase/assets/themes/github_dark.json deleted file mode 100644 index bd4801482e..0000000000 --- a/mobile/packages/ui/showcase/assets/themes/github_dark.json +++ /dev/null @@ -1,339 +0,0 @@ -{ - "name": "GitHub Dark", - "settings": [ - { - "settings": { - "foreground": "#e1e4e8", - "background": "#24292e" - } - }, - { - "scope": [ - "comment", - "punctuation.definition.comment", - "string.comment" - ], - "settings": { - "foreground": "#6a737d" - } - }, - { - "scope": [ - "constant", - "entity.name.constant", - "variable.other.constant", - "variable.other.enummember", - "variable.language" - ], - "settings": { - "foreground": "#79b8ff" - } - }, - { - "scope": [ - "entity", - "entity.name" - ], - "settings": { - "foreground": "#b392f0" - } - }, - { - "scope": "variable.parameter.function", - "settings": { - "foreground": "#e1e4e8" - } - }, - { - "scope": "entity.name.tag", - "settings": { - "foreground": "#85e89d" - } - }, - { - "scope": "keyword", - "settings": { - "foreground": "#f97583" - } - }, - { - "scope": [ - "storage", - "storage.type" - ], - "settings": { - "foreground": "#f97583" - } - }, - { - "scope": [ - "storage.modifier.package", - "storage.modifier.import", - "storage.type.java" - ], - "settings": { - "foreground": "#e1e4e8" - } - }, - { - "scope": [ - "string", - "punctuation.definition.string", - "string punctuation.section.embedded source" - ], - "settings": { - "foreground": "#9ecbff" - } - }, - { - "scope": "support", - "settings": { - "foreground": "#79b8ff" - } - }, - { - "scope": "meta.property-name", - "settings": { - "foreground": "#79b8ff" - } - }, - { - "scope": "variable", - "settings": { - "foreground": "#ffab70" - } - }, - { - "scope": "variable.other", - "settings": { - "foreground": "#e1e4e8" - } - }, - { - "scope": "invalid.broken", - "settings": { - "fontStyle": "italic", - "foreground": "#fdaeb7" - } - }, - { - "scope": "invalid.deprecated", - "settings": { - "fontStyle": "italic", - "foreground": "#fdaeb7" - } - }, - { - "scope": "invalid.illegal", - "settings": { - "fontStyle": "italic", - "foreground": "#fdaeb7" - } - }, - { - "scope": "invalid.unimplemented", - "settings": { - "fontStyle": "italic", - "foreground": "#fdaeb7" - } - }, - { - "scope": "message.error", - "settings": { - "foreground": "#fdaeb7" - } - }, - { - "scope": "string variable", - "settings": { - "foreground": "#79b8ff" - } - }, - { - "scope": [ - "source.regexp", - "string.regexp" - ], - "settings": { - "foreground": "#dbedff" - } - }, - { - "scope": [ - "string.regexp.character-class", - "string.regexp constant.character.escape", - "string.regexp source.ruby.embedded", - "string.regexp string.regexp.arbitrary-repitition" - ], - "settings": { - "foreground": "#dbedff" - } - }, - { - "scope": "string.regexp constant.character.escape", - "settings": { - "fontStyle": "bold", - "foreground": "#85e89d" - } - }, - { - "scope": "support.constant", - "settings": { - "foreground": "#79b8ff" - } - }, - { - "scope": "support.variable", - "settings": { - "foreground": "#79b8ff" - } - }, - { - "scope": "meta.module-reference", - "settings": { - "foreground": "#79b8ff" - } - }, - { - "scope": "punctuation.definition.list.begin.markdown", - "settings": { - "foreground": "#ffab70" - } - }, - { - "scope": [ - "markup.heading", - "markup.heading entity.name" - ], - "settings": { - "fontStyle": "bold", - "foreground": "#79b8ff" - } - }, - { - "scope": "markup.quote", - "settings": { - "foreground": "#85e89d" - } - }, - { - "scope": "markup.italic", - "settings": { - "fontStyle": "italic", - "foreground": "#e1e4e8" - } - }, - { - "scope": "markup.bold", - "settings": { - "fontStyle": "bold", - "foreground": "#e1e4e8" - } - }, - { - "scope": "markup.underline", - "settings": { - "fontStyle": "underline" - } - }, - { - "scope": "markup.inline.raw", - "settings": { - "foreground": "#79b8ff" - } - }, - { - "scope": [ - "markup.deleted", - "meta.diff.header.from-file", - "punctuation.definition.deleted" - ], - "settings": { - "foreground": "#fdaeb7" - } - }, - { - "scope": [ - "markup.inserted", - "meta.diff.header.to-file", - "punctuation.definition.inserted" - ], - "settings": { - "foreground": "#85e89d" - } - }, - { - "scope": [ - "markup.changed", - "punctuation.definition.changed" - ], - "settings": { - "foreground": "#ffab70" - } - }, - { - "scope": [ - "markup.ignored", - "markup.untracked" - ], - "settings": { - "foreground": "#2f363d" - } - }, - { - "scope": "meta.diff.range", - "settings": { - "fontStyle": "bold", - "foreground": "#b392f0" - } - }, - { - "scope": "meta.diff.header", - "settings": { - "foreground": "#79b8ff" - } - }, - { - "scope": "meta.separator", - "settings": { - "fontStyle": "bold", - "foreground": "#79b8ff" - } - }, - { - "scope": "meta.output", - "settings": { - "foreground": "#79b8ff" - } - }, - { - "scope": [ - "brackethighlighter.tag", - "brackethighlighter.curly", - "brackethighlighter.round", - "brackethighlighter.square", - "brackethighlighter.angle", - "brackethighlighter.quote" - ], - "settings": { - "foreground": "#d1d5da" - } - }, - { - "scope": "brackethighlighter.unmatched", - "settings": { - "foreground": "#fdaeb7" - } - }, - { - "scope": [ - "constant.other.reference.link", - "string.other.link" - ], - "settings": { - "fontStyle": "underline", - "foreground": "#dbedff" - } - } - ] -} diff --git a/mobile/packages/ui/showcase/lib/app_theme.dart b/mobile/packages/ui/showcase/lib/app_theme.dart deleted file mode 100644 index 995bf3c91e..0000000000 --- a/mobile/packages/ui/showcase/lib/app_theme.dart +++ /dev/null @@ -1,96 +0,0 @@ -import 'package:flutter/material.dart'; - -class AppTheme { - // Light theme colors - static const _primary500 = Color(0xFF4250AF); - static const _primary100 = Color(0xFFD4D6F0); - static const _primary900 = Color(0xFF181E44); - static const _danger500 = Color(0xFFE53E3E); - static const _light50 = Color(0xFFFAFAFA); - static const _light300 = Color(0xFFD4D4D4); - static const _light500 = Color(0xFF737373); - - // Dark theme colors - static const _darkPrimary500 = Color(0xFFACCBFA); - static const _darkPrimary300 = Color(0xFF616D94); - static const _darkDanger500 = Color(0xFFE88080); - static const _darkLight50 = Color(0xFF0A0A0A); - static const _darkLight100 = Color(0xFF171717); - static const _darkLight200 = Color(0xFF262626); - - static ThemeData get lightTheme { - return ThemeData( - colorScheme: const ColorScheme.light( - primary: _primary500, - onPrimary: Colors.white, - primaryContainer: _primary100, - onPrimaryContainer: _primary900, - secondary: _light500, - onSecondary: Colors.white, - error: _danger500, - onError: Colors.white, - surface: _light50, - onSurface: Color(0xFF1A1C1E), - surfaceContainerHighest: Color(0xFFE3E4E8), - outline: Color(0xFFD1D3D9), - outlineVariant: _light300, - ), - useMaterial3: true, - fontFamily: 'GoogleSans', - scaffoldBackgroundColor: _light50, - cardTheme: const CardThemeData( - elevation: 0, - color: Colors.white, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(12)), - side: BorderSide(color: _light300, width: 1), - ), - ), - appBarTheme: const AppBarTheme( - centerTitle: false, - elevation: 0, - backgroundColor: Colors.white, - surfaceTintColor: Colors.transparent, - foregroundColor: Color(0xFF1A1C1E), - ), - ); - } - - static ThemeData get darkTheme { - return ThemeData( - colorScheme: const ColorScheme.dark( - primary: _darkPrimary500, - onPrimary: Color(0xFF0F1433), - primaryContainer: _darkPrimary300, - onPrimaryContainer: _primary100, - secondary: Color(0xFFC4C6D0), - onSecondary: Color(0xFF2E3042), - error: _darkDanger500, - onError: Color(0xFF0F1433), - surface: _darkLight50, - onSurface: Color(0xFFE3E3E6), - surfaceContainerHighest: _darkLight200, - outline: Color(0xFF8E9099), - outlineVariant: Color(0xFF43464F), - ), - useMaterial3: true, - fontFamily: 'GoogleSans', - scaffoldBackgroundColor: _darkLight50, - cardTheme: const CardThemeData( - elevation: 0, - color: _darkLight100, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(12)), - side: BorderSide(color: _darkLight200, width: 1), - ), - ), - appBarTheme: const AppBarTheme( - centerTitle: false, - elevation: 0, - backgroundColor: _darkLight50, - surfaceTintColor: Colors.transparent, - foregroundColor: Color(0xFFE3E3E6), - ), - ); - } -} diff --git a/mobile/packages/ui/showcase/lib/constants.dart b/mobile/packages/ui/showcase/lib/constants.dart deleted file mode 100644 index cfca4cfda9..0000000000 --- a/mobile/packages/ui/showcase/lib/constants.dart +++ /dev/null @@ -1,16 +0,0 @@ -const String appTitle = '@immich/ui'; - -class LayoutConstants { - static const double sidebarWidth = 220.0; - - static const double gridSpacing = 16.0; - static const double gridAspectRatio = 2.5; - - static const double borderRadiusSmall = 6.0; - static const double borderRadiusMedium = 8.0; - static const double borderRadiusLarge = 12.0; - - static const double iconSizeSmall = 16.0; - static const double iconSizeMedium = 18.0; - static const double iconSizeLarge = 20.0; -} diff --git a/mobile/packages/ui/showcase/lib/main.dart b/mobile/packages/ui/showcase/lib/main.dart deleted file mode 100644 index 6cd2df4fe5..0000000000 --- a/mobile/packages/ui/showcase/lib/main.dart +++ /dev/null @@ -1,55 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; -import 'package:immich_ui/immich_ui.dart'; -import 'package:showcase/app_theme.dart'; -import 'package:showcase/constants.dart'; -import 'package:showcase/router.dart'; -import 'package:showcase/widgets/example_card.dart'; - -void main() async { - WidgetsFlutterBinding.ensureInitialized(); - await initializeCodeHighlighter(); - runApp(const ShowcaseApp()); -} - -class ShowcaseApp extends StatefulWidget { - const ShowcaseApp({super.key}); - - @override - State createState() => _ShowcaseAppState(); -} - -class _ShowcaseAppState extends State { - ThemeMode _themeMode = ThemeMode.light; - late final GoRouter _router; - - @override - void initState() { - super.initState(); - _router = AppRouter.createRouter(_toggleTheme); - } - - void _toggleTheme() { - setState(() { - _themeMode = _themeMode == ThemeMode.light - ? ThemeMode.dark - : ThemeMode.light; - }); - } - - @override - Widget build(BuildContext context) { - return MaterialApp.router( - title: appTitle, - themeMode: _themeMode, - routerConfig: _router, - theme: AppTheme.lightTheme, - darkTheme: AppTheme.darkTheme, - debugShowCheckedModeBanner: false, - builder: (context, child) => ImmichThemeProvider( - colorScheme: Theme.of(context).colorScheme, - child: child!, - ), - ); - } -} diff --git a/mobile/packages/ui/showcase/lib/pages/components/close_button_page.dart b/mobile/packages/ui/showcase/lib/pages/components/close_button_page.dart deleted file mode 100644 index 1bae98e0a4..0000000000 --- a/mobile/packages/ui/showcase/lib/pages/components/close_button_page.dart +++ /dev/null @@ -1,41 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:immich_ui/immich_ui.dart'; -import 'package:showcase/routes.dart'; -import 'package:showcase/widgets/component_examples.dart'; -import 'package:showcase/widgets/example_card.dart'; -import 'package:showcase/widgets/page_title.dart'; - -class CloseButtonPage extends StatelessWidget { - const CloseButtonPage({super.key}); - - @override - Widget build(BuildContext context) { - return PageTitle( - title: AppRoute.closeButton.name, - child: ComponentExamples( - title: 'ImmichCloseButton', - subtitle: 'Pre-configured close button for dialogs and sheets.', - examples: [ - ExampleCard( - title: 'Default & Custom', - preview: Wrap( - spacing: 12, - runSpacing: 12, - children: [ - ImmichCloseButton(onPressed: () {}), - ImmichCloseButton( - variant: ImmichVariant.filled, - onPressed: () {}, - ), - ImmichCloseButton( - color: ImmichColor.secondary, - onPressed: () {}, - ), - ], - ), - ), - ], - ), - ); - } -} diff --git a/mobile/packages/ui/showcase/lib/pages/components/examples/formatted_text_bold_text.dart b/mobile/packages/ui/showcase/lib/pages/components/examples/formatted_text_bold_text.dart deleted file mode 100644 index 7e36ac7537..0000000000 --- a/mobile/packages/ui/showcase/lib/pages/components/examples/formatted_text_bold_text.dart +++ /dev/null @@ -1,11 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:immich_ui/immich_ui.dart'; - -class FormattedTextBoldText extends StatelessWidget { - const FormattedTextBoldText({super.key}); - - @override - Widget build(BuildContext context) { - return ImmichFormattedText('This is bold text.'); - } -} diff --git a/mobile/packages/ui/showcase/lib/pages/components/examples/formatted_text_links.dart b/mobile/packages/ui/showcase/lib/pages/components/examples/formatted_text_links.dart deleted file mode 100644 index 3910a5117a..0000000000 --- a/mobile/packages/ui/showcase/lib/pages/components/examples/formatted_text_links.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:immich_ui/immich_ui.dart'; - -class FormattedTextLinks extends StatelessWidget { - const FormattedTextLinks({super.key}); - - @override - Widget build(BuildContext context) { - return ImmichFormattedText( - 'Read the documentation or visit GitHub.', - spanBuilder: (tag) => FormattedSpan( - onTap: switch (tag) { - 'docs-link' => () => ScaffoldMessenger.of( - context, - ).showSnackBar(const SnackBar(content: Text('Docs link clicked!'))), - 'github-link' => () => ScaffoldMessenger.of( - context, - ).showSnackBar(const SnackBar(content: Text('GitHub link clicked!'))), - _ => null, - }, - ), - ); - } -} diff --git a/mobile/packages/ui/showcase/lib/pages/components/examples/formatted_text_mixed_tags.dart b/mobile/packages/ui/showcase/lib/pages/components/examples/formatted_text_mixed_tags.dart deleted file mode 100644 index 3490b1c386..0000000000 --- a/mobile/packages/ui/showcase/lib/pages/components/examples/formatted_text_mixed_tags.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:immich_ui/immich_ui.dart'; - -class FormattedTextMixedContent extends StatelessWidget { - const FormattedTextMixedContent({super.key}); - - @override - Widget build(BuildContext context) { - return ImmichFormattedText( - 'You can use bold text and links together.', - spanBuilder: (tag) => switch (tag) { - 'b' => const FormattedSpan( - style: TextStyle(fontWeight: FontWeight.bold), - ), - _ => FormattedSpan( - onTap: () => ScaffoldMessenger.of( - context, - ).showSnackBar(const SnackBar(content: Text('Link clicked!'))), - ), - }, - ); - } -} diff --git a/mobile/packages/ui/showcase/lib/pages/components/form_page.dart b/mobile/packages/ui/showcase/lib/pages/components/form_page.dart deleted file mode 100644 index 14567031de..0000000000 --- a/mobile/packages/ui/showcase/lib/pages/components/form_page.dart +++ /dev/null @@ -1,79 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:immich_ui/immich_ui.dart'; -import 'package:showcase/routes.dart'; -import 'package:showcase/widgets/component_examples.dart'; -import 'package:showcase/widgets/example_card.dart'; -import 'package:showcase/widgets/page_title.dart'; - -class FormPage extends StatefulWidget { - const FormPage({super.key}); - - @override - State createState() => _FormPageState(); -} - -class _FormPageState extends State { - final _emailController = TextEditingController(); - final _passwordController = TextEditingController(); - String _result = ''; - - @override - Widget build(BuildContext context) { - return PageTitle( - title: AppRoute.form.name, - child: ComponentExamples( - title: 'ImmichForm', - subtitle: - 'Form container with built-in validation and submit handling.', - examples: [ - ExampleCard( - title: 'Login Form', - preview: Column( - children: [ - ImmichForm( - submitText: 'Login', - submitIcon: Icons.login, - onSubmit: () async { - await Future.delayed(const Duration(seconds: 1)); - setState(() { - _result = 'Form submitted!'; - }); - }, - child: Column( - spacing: 10, - children: [ - ImmichTextInput( - label: 'Email', - controller: _emailController, - keyboardType: TextInputType.emailAddress, - validator: (value) => - value?.isEmpty ?? true ? 'Required' : null, - ), - ImmichPasswordInput( - label: 'Password', - controller: _passwordController, - validator: (value) => - value?.isEmpty ?? true ? 'Required' : null, - ), - ], - ), - ), - if (_result.isNotEmpty) ...[ - const SizedBox(height: 16), - Text(_result, style: const TextStyle(color: Colors.green)), - ], - ], - ), - ), - ], - ), - ); - } - - @override - void dispose() { - _emailController.dispose(); - _passwordController.dispose(); - super.dispose(); - } -} diff --git a/mobile/packages/ui/showcase/lib/pages/components/formatted_text_page.dart b/mobile/packages/ui/showcase/lib/pages/components/formatted_text_page.dart deleted file mode 100644 index b827e0340b..0000000000 --- a/mobile/packages/ui/showcase/lib/pages/components/formatted_text_page.dart +++ /dev/null @@ -1,40 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:showcase/pages/components/examples/formatted_text_bold_text.dart'; -import 'package:showcase/pages/components/examples/formatted_text_links.dart'; -import 'package:showcase/pages/components/examples/formatted_text_mixed_tags.dart'; -import 'package:showcase/routes.dart'; -import 'package:showcase/widgets/component_examples.dart'; -import 'package:showcase/widgets/example_card.dart'; -import 'package:showcase/widgets/page_title.dart'; - -class FormattedTextPage extends StatelessWidget { - const FormattedTextPage({super.key}); - - @override - Widget build(BuildContext context) { - return PageTitle( - title: AppRoute.formattedText.name, - child: ComponentExamples( - title: 'ImmichFormattedText', - subtitle: 'Render text with HTML formatting (bold, links).', - examples: [ - ExampleCard( - title: 'Bold Text', - preview: const FormattedTextBoldText(), - code: 'formatted_text_bold_text.dart', - ), - ExampleCard( - title: 'Links', - preview: const FormattedTextLinks(), - code: 'formatted_text_links.dart', - ), - ExampleCard( - title: 'Mixed Content', - preview: const FormattedTextMixedContent(), - code: 'formatted_text_mixed_tags.dart', - ), - ], - ), - ); - } -} diff --git a/mobile/packages/ui/showcase/lib/pages/components/icon_button_page.dart b/mobile/packages/ui/showcase/lib/pages/components/icon_button_page.dart deleted file mode 100644 index 4418b1de4f..0000000000 --- a/mobile/packages/ui/showcase/lib/pages/components/icon_button_page.dart +++ /dev/null @@ -1,52 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:immich_ui/immich_ui.dart'; -import 'package:showcase/routes.dart'; -import 'package:showcase/widgets/component_examples.dart'; -import 'package:showcase/widgets/example_card.dart'; -import 'package:showcase/widgets/page_title.dart'; - -class IconButtonPage extends StatelessWidget { - const IconButtonPage({super.key}); - - @override - Widget build(BuildContext context) { - return PageTitle( - title: AppRoute.iconButton.name, - child: ComponentExamples( - title: 'ImmichIconButton', - subtitle: 'Icon-only button with customizable styling.', - examples: [ - ExampleCard( - title: 'Variants & Colors', - preview: Wrap( - spacing: 12, - runSpacing: 12, - children: [ - ImmichIconButton( - icon: Icons.add, - onPressed: () {}, - variant: ImmichVariant.filled, - ), - ImmichIconButton( - icon: Icons.edit, - onPressed: () {}, - variant: ImmichVariant.ghost, - ), - ImmichIconButton( - icon: Icons.delete, - onPressed: () {}, - color: ImmichColor.secondary, - ), - ImmichIconButton( - icon: Icons.settings, - onPressed: () {}, - disabled: true, - ), - ], - ), - ), - ], - ), - ); - } -} diff --git a/mobile/packages/ui/showcase/lib/pages/components/password_input_page.dart b/mobile/packages/ui/showcase/lib/pages/components/password_input_page.dart deleted file mode 100644 index 772dd7882f..0000000000 --- a/mobile/packages/ui/showcase/lib/pages/components/password_input_page.dart +++ /dev/null @@ -1,39 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:immich_ui/immich_ui.dart'; -import 'package:showcase/routes.dart'; -import 'package:showcase/widgets/component_examples.dart'; -import 'package:showcase/widgets/example_card.dart'; -import 'package:showcase/widgets/page_title.dart'; - -class PasswordInputPage extends StatelessWidget { - const PasswordInputPage({super.key}); - - @override - Widget build(BuildContext context) { - return PageTitle( - title: AppRoute.passwordInput.name, - child: ComponentExamples( - title: 'ImmichPasswordInput', - subtitle: 'Password field with visibility toggle.', - examples: [ - ExampleCard( - title: 'Password Input', - preview: ImmichPasswordInput( - label: 'Password', - hintText: 'Enter your password', - validator: (value) { - if (value == null || value.isEmpty) { - return 'Password is required'; - } - if (value.length < 8) { - return 'Password must be at least 8 characters'; - } - return null; - }, - ), - ), - ], - ), - ); - } -} diff --git a/mobile/packages/ui/showcase/lib/pages/components/text_button_page.dart b/mobile/packages/ui/showcase/lib/pages/components/text_button_page.dart deleted file mode 100644 index 59e5b86294..0000000000 --- a/mobile/packages/ui/showcase/lib/pages/components/text_button_page.dart +++ /dev/null @@ -1,140 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:immich_ui/immich_ui.dart'; -import 'package:showcase/routes.dart'; -import 'package:showcase/widgets/component_examples.dart'; -import 'package:showcase/widgets/example_card.dart'; -import 'package:showcase/widgets/page_title.dart'; - -class TextButtonPage extends StatefulWidget { - const TextButtonPage({super.key}); - - @override - State createState() => _TextButtonPageState(); -} - -class _TextButtonPageState extends State { - bool _isLoading = false; - @override - Widget build(BuildContext context) { - return PageTitle( - title: AppRoute.textButton.name, - child: ComponentExamples( - title: 'ImmichTextButton', - subtitle: - 'A versatile button component with multiple variants and color options.', - examples: [ - ExampleCard( - title: 'Variants', - description: - 'Filled and ghost variants for different visual hierarchy', - preview: Wrap( - spacing: 12, - runSpacing: 12, - children: [ - ImmichTextButton( - onPressed: () {}, - labelText: 'Filled', - variant: ImmichVariant.filled, - expanded: false, - ), - ImmichTextButton( - onPressed: () {}, - labelText: 'Ghost', - variant: ImmichVariant.ghost, - expanded: false, - ), - ], - ), - ), - ExampleCard( - title: 'Colors', - description: 'Primary and secondary color options', - preview: Wrap( - spacing: 12, - runSpacing: 12, - children: [ - ImmichTextButton( - onPressed: () {}, - labelText: 'Primary', - color: ImmichColor.primary, - expanded: false, - ), - ImmichTextButton( - onPressed: () {}, - labelText: 'Secondary', - color: ImmichColor.secondary, - expanded: false, - ), - ], - ), - ), - ExampleCard( - title: 'With Icons', - description: 'Add leading icons', - preview: Wrap( - spacing: 12, - runSpacing: 12, - children: [ - ImmichTextButton( - onPressed: () {}, - labelText: 'With Icon', - icon: Icons.add, - expanded: false, - ), - ImmichTextButton( - onPressed: () {}, - labelText: 'Download', - icon: Icons.download, - variant: ImmichVariant.ghost, - expanded: false, - ), - ], - ), - ), - ExampleCard( - title: 'Loading State', - description: 'Shows loading indicator during async operations', - preview: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ImmichTextButton( - onPressed: () async { - setState(() => _isLoading = true); - await Future.delayed(const Duration(seconds: 2)); - if (mounted) setState(() => _isLoading = false); - }, - labelText: _isLoading ? 'Loading...' : 'Click Me', - loading: _isLoading, - expanded: false, - ), - ], - ), - ), - ExampleCard( - title: 'Disabled State', - description: 'Buttons can be disabled', - preview: Wrap( - spacing: 12, - runSpacing: 12, - children: [ - ImmichTextButton( - onPressed: () {}, - labelText: 'Disabled', - disabled: true, - expanded: false, - ), - ImmichTextButton( - onPressed: () {}, - labelText: 'Disabled Ghost', - variant: ImmichVariant.ghost, - disabled: true, - expanded: false, - ), - ], - ), - ), - ], - ), - ); - } -} diff --git a/mobile/packages/ui/showcase/lib/pages/components/text_input_page.dart b/mobile/packages/ui/showcase/lib/pages/components/text_input_page.dart deleted file mode 100644 index 5a0bfec6cd..0000000000 --- a/mobile/packages/ui/showcase/lib/pages/components/text_input_page.dart +++ /dev/null @@ -1,65 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:immich_ui/immich_ui.dart'; -import 'package:showcase/routes.dart'; -import 'package:showcase/widgets/component_examples.dart'; -import 'package:showcase/widgets/example_card.dart'; -import 'package:showcase/widgets/page_title.dart'; - -class TextInputPage extends StatefulWidget { - const TextInputPage({super.key}); - - @override - State createState() => _TextInputPageState(); -} - -class _TextInputPageState extends State { - final _controller1 = TextEditingController(); - final _controller2 = TextEditingController(); - - @override - Widget build(BuildContext context) { - return PageTitle( - title: AppRoute.textInput.name, - child: ComponentExamples( - title: 'ImmichTextInput', - subtitle: 'Text field with validation support.', - examples: [ - ExampleCard( - title: 'Basic Usage', - preview: Column( - children: [ - ImmichTextInput( - label: 'Email', - hintText: 'Enter your email', - controller: _controller1, - keyboardType: TextInputType.emailAddress, - ), - const SizedBox(height: 16), - ImmichTextInput( - label: 'Username', - controller: _controller2, - validator: (value) { - if (value == null || value.isEmpty) { - return 'Username is required'; - } - if (value.length < 3) { - return 'Username must be at least 3 characters'; - } - return null; - }, - ), - ], - ), - ), - ], - ), - ); - } - - @override - void dispose() { - _controller1.dispose(); - _controller2.dispose(); - super.dispose(); - } -} diff --git a/mobile/packages/ui/showcase/lib/pages/design_system/constants_page.dart b/mobile/packages/ui/showcase/lib/pages/design_system/constants_page.dart deleted file mode 100644 index 17de02d80a..0000000000 --- a/mobile/packages/ui/showcase/lib/pages/design_system/constants_page.dart +++ /dev/null @@ -1,396 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:immich_ui/immich_ui.dart'; -import 'package:showcase/routes.dart'; -import 'package:showcase/widgets/component_examples.dart'; -import 'package:showcase/widgets/example_card.dart'; -import 'package:showcase/widgets/page_title.dart'; - -class ConstantsPage extends StatefulWidget { - const ConstantsPage({super.key}); - - @override - State createState() => _ConstantsPageState(); -} - -class _ConstantsPageState extends State { - @override - Widget build(BuildContext context) { - return PageTitle( - title: AppRoute.constants.name, - child: ComponentExamples( - title: 'Constants', - subtitle: 'Consistent spacing, sizing, and styling constants.', - expand: true, - examples: [ - const ExampleCard( - title: 'Spacing', - description: 'ImmichSpacing (4.0 โ†’ 48.0)', - preview: Column( - children: [ - _SpacingBox(label: 'xs', size: ImmichSpacing.xs), - _SpacingBox(label: 'sm', size: ImmichSpacing.sm), - _SpacingBox(label: 'md', size: ImmichSpacing.md), - _SpacingBox(label: 'lg', size: ImmichSpacing.lg), - _SpacingBox(label: 'xl', size: ImmichSpacing.xl), - _SpacingBox(label: 'xxl', size: ImmichSpacing.xxl), - _SpacingBox(label: 'xxxl', size: ImmichSpacing.xxxl), - ], - ), - ), - const ExampleCard( - title: 'Border Radius', - description: 'ImmichRadius (0.0 โ†’ 24.0)', - preview: Wrap( - spacing: 12, - runSpacing: 12, - children: [ - _RadiusBox(label: 'none', radius: ImmichRadius.none), - _RadiusBox(label: 'xs', radius: ImmichRadius.xs), - _RadiusBox(label: 'sm', radius: ImmichRadius.sm), - _RadiusBox(label: 'md', radius: ImmichRadius.md), - _RadiusBox(label: 'lg', radius: ImmichRadius.lg), - _RadiusBox(label: 'xl', radius: ImmichRadius.xl), - _RadiusBox(label: 'xxl', radius: ImmichRadius.xxl), - ], - ), - ), - const ExampleCard( - title: 'Icon Sizes', - description: 'ImmichIconSize (16.0 โ†’ 48.0)', - preview: Wrap( - spacing: 16, - runSpacing: 16, - alignment: WrapAlignment.start, - children: [ - _IconSizeBox(label: 'xs', size: ImmichIconSize.xs), - _IconSizeBox(label: 'sm', size: ImmichIconSize.sm), - _IconSizeBox(label: 'md', size: ImmichIconSize.md), - _IconSizeBox(label: 'lg', size: ImmichIconSize.lg), - _IconSizeBox(label: 'xl', size: ImmichIconSize.xl), - _IconSizeBox(label: 'xxl', size: ImmichIconSize.xxl), - ], - ), - ), - const ExampleCard( - title: 'Text Sizes', - description: 'ImmichTextSize (10.0 โ†’ 60.0)', - preview: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Caption', - style: TextStyle(fontSize: ImmichTextSize.caption), - ), - Text('Label', style: TextStyle(fontSize: ImmichTextSize.label)), - Text('Body', style: TextStyle(fontSize: ImmichTextSize.body)), - Text('H6', style: TextStyle(fontSize: ImmichTextSize.h6)), - Text('H5', style: TextStyle(fontSize: ImmichTextSize.h5)), - Text('H4', style: TextStyle(fontSize: ImmichTextSize.h4)), - Text('H3', style: TextStyle(fontSize: ImmichTextSize.h3)), - Text('H2', style: TextStyle(fontSize: ImmichTextSize.h2)), - Text('H1', style: TextStyle(fontSize: ImmichTextSize.h1)), - ], - ), - ), - const ExampleCard( - title: 'Elevation', - description: 'ImmichElevation (0.0 โ†’ 16.0)', - preview: Wrap( - spacing: 12, - runSpacing: 12, - children: [ - _ElevationBox(label: 'none', elevation: ImmichElevation.none), - _ElevationBox(label: 'xs', elevation: ImmichElevation.xs), - _ElevationBox(label: 'sm', elevation: ImmichElevation.sm), - _ElevationBox(label: 'md', elevation: ImmichElevation.md), - _ElevationBox(label: 'lg', elevation: ImmichElevation.lg), - _ElevationBox(label: 'xl', elevation: ImmichElevation.xl), - _ElevationBox(label: 'xxl', elevation: ImmichElevation.xxl), - ], - ), - ), - const ExampleCard( - title: 'Border Width', - description: 'ImmichBorderWidth (0.5 โ†’ 4.0)', - preview: Column( - children: [ - _BorderBox( - label: 'hairline', - borderWidth: ImmichBorderWidth.hairline, - ), - _BorderBox(label: 'base', borderWidth: ImmichBorderWidth.base), - _BorderBox(label: 'md', borderWidth: ImmichBorderWidth.md), - _BorderBox(label: 'lg', borderWidth: ImmichBorderWidth.lg), - _BorderBox(label: 'xl', borderWidth: ImmichBorderWidth.xl), - ], - ), - ), - const ExampleCard( - title: 'Animation Durations', - description: 'ImmichDuration (100ms โ†’ 700ms)', - preview: Column( - crossAxisAlignment: CrossAxisAlignment.start, - spacing: 8, - children: [ - _AnimatedDurationBox( - label: 'Extra Fast', - duration: ImmichDuration.extraFast, - ), - _AnimatedDurationBox( - label: 'Fast', - duration: ImmichDuration.fast, - ), - _AnimatedDurationBox( - label: 'Normal', - duration: ImmichDuration.normal, - ), - _AnimatedDurationBox( - label: 'Slow', - duration: ImmichDuration.slow, - ), - _AnimatedDurationBox( - label: 'Extra Slow', - duration: ImmichDuration.extraSlow, - ), - ], - ), - ), - ], - ), - ); - } -} - -class _SpacingBox extends StatelessWidget { - final String label; - final double size; - - const _SpacingBox({required this.label, required this.size}); - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 4), - child: Row( - children: [ - SizedBox( - width: 60, - child: Text( - label, - style: const TextStyle(fontFamily: 'GoogleSansCode'), - ), - ), - Container( - width: size, - height: 24, - color: Theme.of(context).colorScheme.primary, - ), - const SizedBox(width: 8), - Text('${size.toStringAsFixed(1)}px'), - ], - ), - ); - } -} - -class _RadiusBox extends StatelessWidget { - final String label; - final double radius; - - const _RadiusBox({required this.label, required this.radius}); - - @override - Widget build(BuildContext context) { - return Column( - children: [ - Container( - width: 60, - height: 60, - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.primary, - borderRadius: BorderRadius.circular(radius), - ), - ), - const SizedBox(height: 4), - Text(label, style: const TextStyle(fontSize: 12)), - ], - ); - } -} - -class _IconSizeBox extends StatelessWidget { - final String label; - final double size; - - const _IconSizeBox({required this.label, required this.size}); - - @override - Widget build(BuildContext context) { - return Column( - children: [ - Icon(Icons.palette_rounded, size: size), - const SizedBox(height: 4), - Text(label, style: const TextStyle(fontSize: 12)), - Text( - '${size.toStringAsFixed(0)}px', - style: const TextStyle(fontSize: 10, color: Colors.grey), - ), - ], - ); - } -} - -class _ElevationBox extends StatelessWidget { - final String label; - final double elevation; - - const _ElevationBox({required this.label, required this.elevation}); - - @override - Widget build(BuildContext context) { - return Column( - children: [ - Material( - elevation: elevation, - borderRadius: const BorderRadius.all(Radius.circular(8)), - child: Container( - width: 60, - height: 60, - alignment: Alignment.center, - child: Text(label, style: const TextStyle(fontSize: 12)), - ), - ), - const SizedBox(height: 4), - Text( - elevation.toStringAsFixed(1), - style: const TextStyle(fontSize: 10), - ), - ], - ); - } -} - -class _BorderBox extends StatelessWidget { - final String label; - final double borderWidth; - - const _BorderBox({required this.label, required this.borderWidth}); - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 4), - child: Row( - children: [ - SizedBox( - width: 80, - child: Text( - label, - style: const TextStyle(fontFamily: 'GoogleSansCode'), - ), - ), - Expanded( - child: Container( - height: 40, - decoration: BoxDecoration( - border: Border.all( - color: Theme.of(context).colorScheme.primary, - width: borderWidth, - ), - borderRadius: const BorderRadius.all(Radius.circular(4)), - ), - ), - ), - const SizedBox(width: 8), - Text('${borderWidth.toStringAsFixed(1)}px'), - ], - ), - ); - } -} - -class _AnimatedDurationBox extends StatefulWidget { - final String label; - final Duration duration; - - const _AnimatedDurationBox({required this.label, required this.duration}); - - @override - State<_AnimatedDurationBox> createState() => _AnimatedDurationBoxState(); -} - -class _AnimatedDurationBoxState extends State<_AnimatedDurationBox> { - bool _atEnd = false; - bool _isAnimating = false; - - void _playAnimation() async { - if (_isAnimating) return; - setState(() => _isAnimating = true); - setState(() => _atEnd = true); - await Future.delayed(widget.duration); - if (!mounted) return; - setState(() => _atEnd = false); - await Future.delayed(widget.duration); - if (!mounted) return; - setState(() => _isAnimating = false); - } - - @override - Widget build(BuildContext context) { - final colorScheme = Theme.of(context).colorScheme; - return Row( - children: [ - SizedBox( - width: 90, - child: Text( - widget.label, - style: const TextStyle(fontFamily: 'GoogleSansCode', fontSize: 12), - ), - ), - Expanded( - child: Container( - height: 32, - decoration: BoxDecoration( - color: colorScheme.surfaceContainerHighest.withValues(alpha: 0.5), - borderRadius: BorderRadius.circular(6), - ), - child: AnimatedAlign( - duration: widget.duration, - curve: Curves.easeInOut, - alignment: _atEnd ? Alignment.centerRight : Alignment.centerLeft, - child: Container( - width: 60, - height: 28, - margin: const EdgeInsets.symmetric(horizontal: 2), - decoration: BoxDecoration( - color: colorScheme.primary, - borderRadius: BorderRadius.circular(4), - ), - alignment: Alignment.center, - child: Text( - '${widget.duration.inMilliseconds}ms', - style: TextStyle( - fontSize: 11, - color: colorScheme.onPrimary, - fontWeight: FontWeight.w500, - ), - ), - ), - ), - ), - ), - const SizedBox(width: 8), - IconButton( - onPressed: _isAnimating ? null : _playAnimation, - icon: Icon( - Icons.play_arrow_rounded, - color: _isAnimating ? colorScheme.outline : colorScheme.primary, - ), - iconSize: 24, - padding: EdgeInsets.zero, - constraints: const BoxConstraints(minWidth: 32, minHeight: 32), - ), - ], - ); - } -} diff --git a/mobile/packages/ui/showcase/lib/pages/home_page.dart b/mobile/packages/ui/showcase/lib/pages/home_page.dart deleted file mode 100644 index de7af6c26b..0000000000 --- a/mobile/packages/ui/showcase/lib/pages/home_page.dart +++ /dev/null @@ -1,118 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; -import 'package:showcase/constants.dart'; -import 'package:showcase/routes.dart'; - -class HomePage extends StatelessWidget { - final VoidCallback onThemeToggle; - - const HomePage({super.key, required this.onThemeToggle}); - - @override - Widget build(BuildContext context) { - return Title( - title: appTitle, - color: Theme.of(context).colorScheme.primary, - child: ListView( - padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 32), - children: [ - Text( - appTitle, - style: Theme.of(context).textTheme.displaySmall?.copyWith( - fontWeight: FontWeight.bold, - color: Theme.of(context).colorScheme.onSurface, - ), - ), - const SizedBox(height: 12), - Text( - 'A collection of Flutter components that are shared across all Immich projects', - style: Theme.of(context).textTheme.titleMedium?.copyWith( - color: Theme.of(context).colorScheme.onSurfaceVariant, - fontWeight: FontWeight.w400, - height: 1.5, - ), - ), - const SizedBox(height: 48), - ...routesByCategory.entries.map((entry) { - if (entry.key == AppRouteCategory.root) { - return const SizedBox.shrink(); - } - - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - entry.key.displayName, - style: Theme.of(context).textTheme.headlineSmall?.copyWith( - fontWeight: FontWeight.w600, - color: Theme.of(context).colorScheme.onSurface, - ), - ), - const SizedBox(height: 16), - GridView.builder( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 3, - crossAxisSpacing: LayoutConstants.gridSpacing, - mainAxisSpacing: LayoutConstants.gridSpacing, - childAspectRatio: LayoutConstants.gridAspectRatio, - ), - itemCount: entry.value.length, - itemBuilder: (context, index) { - return _ComponentCard(route: entry.value[index]); - }, - ), - const SizedBox(height: 48), - ], - ); - }), - ], - ), - ); - } -} - -class _ComponentCard extends StatelessWidget { - final AppRoute route; - - const _ComponentCard({required this.route}); - - @override - Widget build(BuildContext context) { - return InkWell( - onTap: () => context.go(route.path), - borderRadius: const BorderRadius.all(Radius.circular(LayoutConstants.borderRadiusLarge)), - child: Card( - child: Padding( - padding: const EdgeInsets.all(20), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Icon(route.icon, size: 32, color: Theme.of(context).colorScheme.primary), - const SizedBox(height: 16), - Text( - route.name, - style: Theme.of(context).textTheme.titleMedium?.copyWith( - fontWeight: FontWeight.w600, - color: Theme.of(context).colorScheme.onSurface, - ), - ), - - const SizedBox(height: 8), - Text( - route.description, - style: Theme.of( - context, - ).textTheme.bodyMedium?.copyWith(color: Theme.of(context).colorScheme.onSurfaceVariant, height: 1.4), - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - ], - ), - ), - ), - ); - } -} diff --git a/mobile/packages/ui/showcase/lib/router.dart b/mobile/packages/ui/showcase/lib/router.dart deleted file mode 100644 index 34393da508..0000000000 --- a/mobile/packages/ui/showcase/lib/router.dart +++ /dev/null @@ -1,48 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; -import 'package:showcase/pages/components/close_button_page.dart'; -import 'package:showcase/pages/components/form_page.dart'; -import 'package:showcase/pages/components/formatted_text_page.dart'; -import 'package:showcase/pages/components/icon_button_page.dart'; -import 'package:showcase/pages/components/password_input_page.dart'; -import 'package:showcase/pages/components/text_button_page.dart'; -import 'package:showcase/pages/components/text_input_page.dart'; -import 'package:showcase/pages/design_system/constants_page.dart'; -import 'package:showcase/pages/home_page.dart'; -import 'package:showcase/routes.dart'; -import 'package:showcase/widgets/shell_layout.dart'; - -class AppRouter { - static GoRouter createRouter(VoidCallback onThemeToggle) { - return GoRouter( - initialLocation: AppRoute.home.path, - routes: [ - ShellRoute( - builder: (context, state, child) => - ShellLayout(onThemeToggle: onThemeToggle, child: child), - routes: AppRoute.values - .map( - (route) => GoRoute( - path: route.path, - pageBuilder: (context, state) => NoTransitionPage( - key: state.pageKey, - child: switch (route) { - AppRoute.home => HomePage(onThemeToggle: onThemeToggle), - AppRoute.textButton => const TextButtonPage(), - AppRoute.iconButton => const IconButtonPage(), - AppRoute.closeButton => const CloseButtonPage(), - AppRoute.textInput => const TextInputPage(), - AppRoute.passwordInput => const PasswordInputPage(), - AppRoute.form => const FormPage(), - AppRoute.formattedText => const FormattedTextPage(), - AppRoute.constants => const ConstantsPage(), - }, - ), - ), - ) - .toList(), - ), - ], - ); - } -} diff --git a/mobile/packages/ui/showcase/lib/routes.dart b/mobile/packages/ui/showcase/lib/routes.dart deleted file mode 100644 index 4feeeafdb6..0000000000 --- a/mobile/packages/ui/showcase/lib/routes.dart +++ /dev/null @@ -1,97 +0,0 @@ -import 'package:flutter/material.dart'; - -enum AppRouteCategory { - root(''), - forms('Forms'), - buttons('Buttons'), - designSystem('Design System'); - - final String displayName; - const AppRouteCategory(this.displayName); -} - -enum AppRoute { - home( - name: 'Home', - description: 'Home page', - path: '/', - category: AppRouteCategory.root, - icon: Icons.home_outlined, - ), - textButton( - name: 'Text Button', - description: 'Versatile button with filled and ghost variants', - path: '/text-button', - category: AppRouteCategory.buttons, - icon: Icons.smart_button_rounded, - ), - iconButton( - name: 'Icon Button', - description: 'Icon-only button with customizable styling', - path: '/icon-button', - category: AppRouteCategory.buttons, - icon: Icons.radio_button_unchecked_rounded, - ), - closeButton( - name: 'Close Button', - description: 'Pre-configured close button for dialogs', - path: '/close-button', - category: AppRouteCategory.buttons, - icon: Icons.close_rounded, - ), - textInput( - name: 'Text Input', - description: 'Text field with validation support', - path: '/text-input', - category: AppRouteCategory.forms, - icon: Icons.text_fields_outlined, - ), - passwordInput( - name: 'Password Input', - description: 'Password field with visibility toggle', - path: '/password-input', - category: AppRouteCategory.forms, - icon: Icons.password_outlined, - ), - form( - name: 'Form', - description: 'Form container with built-in validation', - path: '/form', - category: AppRouteCategory.forms, - icon: Icons.description_outlined, - ), - formattedText( - name: 'Formatted Text', - description: 'Render text with HTML formatting', - path: '/formatted-text', - category: AppRouteCategory.forms, - icon: Icons.code_rounded, - ), - constants( - name: 'Constants', - description: 'Spacing, colors, typography, and more', - path: '/constants', - category: AppRouteCategory.designSystem, - icon: Icons.palette_outlined, - ); - - final String name; - final String description; - final String path; - final AppRouteCategory category; - final IconData icon; - - const AppRoute({ - required this.name, - required this.description, - required this.path, - required this.category, - required this.icon, - }); -} - -final routesByCategory = AppRoute.values - .fold>>({}, (map, route) { - map.putIfAbsent(route.category, () => []).add(route); - return map; - }); diff --git a/mobile/packages/ui/showcase/lib/widgets/component_examples.dart b/mobile/packages/ui/showcase/lib/widgets/component_examples.dart deleted file mode 100644 index 21e6516079..0000000000 --- a/mobile/packages/ui/showcase/lib/widgets/component_examples.dart +++ /dev/null @@ -1,85 +0,0 @@ -import 'package:flutter/material.dart'; - -class ComponentExamples extends StatelessWidget { - final String title; - final String? subtitle; - final List examples; - final bool expand; - - const ComponentExamples({ - super.key, - required this.title, - this.subtitle, - required this.examples, - this.expand = false, - }); - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.fromLTRB(10, 24, 24, 24), - child: CustomScrollView( - slivers: [ - SliverToBoxAdapter( - child: _PageHeader(title: title, subtitle: subtitle), - ), - const SliverPadding(padding: EdgeInsets.only(top: 24)), - if (expand) - SliverList.builder( - itemCount: examples.length, - itemBuilder: (context, index) => examples[index], - ) - else - SliverLayoutBuilder( - builder: (context, constraints) { - return SliverList.builder( - itemCount: examples.length, - itemBuilder: (context, index) => Align( - alignment: Alignment.centerLeft, - child: ConstrainedBox( - constraints: BoxConstraints( - minWidth: constraints.crossAxisExtent * 0.6, - maxWidth: constraints.crossAxisExtent, - ), - child: IntrinsicWidth(child: examples[index]), - ), - ), - ); - }, - ), - ], - ), - ); - } -} - -class _PageHeader extends StatelessWidget { - final String title; - final String? subtitle; - - const _PageHeader({required this.title, this.subtitle}); - - @override - Widget build(BuildContext context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - title, - style: Theme.of( - context, - ).textTheme.headlineLarge?.copyWith(fontWeight: FontWeight.bold), - ), - if (subtitle != null) ...[ - const SizedBox(height: 8), - Text( - subtitle!, - style: Theme.of(context).textTheme.bodyLarge?.copyWith( - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ), - ], - ], - ); - } -} diff --git a/mobile/packages/ui/showcase/lib/widgets/example_card.dart b/mobile/packages/ui/showcase/lib/widgets/example_card.dart deleted file mode 100644 index fea561afb6..0000000000 --- a/mobile/packages/ui/showcase/lib/widgets/example_card.dart +++ /dev/null @@ -1,237 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:showcase/constants.dart'; -import 'package:syntax_highlight/syntax_highlight.dart'; - -late final Highlighter _codeHighlighter; - -Future initializeCodeHighlighter() async { - await Highlighter.initialize(['dart']); - final darkTheme = await HighlighterTheme.loadFromAssets([ - 'assets/themes/github_dark.json', - ], const TextStyle(color: Color(0xFFe1e4e8))); - - _codeHighlighter = Highlighter(language: 'dart', theme: darkTheme); -} - -class ExampleCard extends StatefulWidget { - final String title; - final String? description; - final Widget preview; - final String? code; - - const ExampleCard({ - super.key, - required this.title, - this.description, - required this.preview, - this.code, - }); - - @override - State createState() => _ExampleCardState(); -} - -class _ExampleCardState extends State { - bool _showPreview = true; - String? code; - - @override - void initState() { - super.initState(); - if (widget.code != null) { - rootBundle - .loadString('lib/pages/components/examples/${widget.code!}') - .then((value) { - setState(() { - code = value; - }); - }); - } - } - - @override - Widget build(BuildContext context) { - return Card( - elevation: 1, - margin: const EdgeInsets.only(bottom: 16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.all(16.0), - child: Row( - children: [ - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - widget.title, - style: Theme.of(context).textTheme.titleMedium - ?.copyWith(fontWeight: FontWeight.bold), - ), - const SizedBox(height: 4), - if (widget.description != null) - Text( - widget.description!, - style: Theme.of(context).textTheme.bodyMedium - ?.copyWith( - color: Theme.of( - context, - ).colorScheme.onSurfaceVariant, - ), - ), - ], - ), - ), - if (code != null) ...[ - const SizedBox(width: 16), - Row( - children: [ - _ToggleButton( - icon: Icons.visibility_rounded, - label: 'Preview', - isSelected: _showPreview, - onTap: () => setState(() => _showPreview = true), - ), - const SizedBox(width: 8), - _ToggleButton( - icon: Icons.code_rounded, - label: 'Code', - isSelected: !_showPreview, - onTap: () => setState(() => _showPreview = false), - ), - ], - ), - ], - ], - ), - ), - const Divider(height: 1), - if (_showPreview) - Padding( - padding: const EdgeInsets.all(16.0), - child: SizedBox(width: double.infinity, child: widget.preview), - ) - else - Container( - width: double.infinity, - decoration: const BoxDecoration( - color: Color(0xFF24292e), - borderRadius: BorderRadius.only( - bottomLeft: Radius.circular( - LayoutConstants.borderRadiusMedium, - ), - bottomRight: Radius.circular( - LayoutConstants.borderRadiusMedium, - ), - ), - ), - child: _CodeCard(code: code!), - ), - ], - ), - ); - } -} - -class _ToggleButton extends StatelessWidget { - final IconData icon; - final String label; - final bool isSelected; - final VoidCallback onTap; - - const _ToggleButton({ - required this.icon, - required this.label, - required this.isSelected, - required this.onTap, - }); - - @override - Widget build(BuildContext context) { - return InkWell( - onTap: onTap, - borderRadius: const BorderRadius.all(Radius.circular(24)), - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), - decoration: BoxDecoration( - color: isSelected - ? Theme.of(context).colorScheme.primary.withValues(alpha: 0.7) - : Theme.of(context).colorScheme.primary, - borderRadius: const BorderRadius.all(Radius.circular(24)), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - icon, - size: 16, - color: Theme.of(context).colorScheme.onPrimary, - ), - const SizedBox(width: 6), - Text( - label, - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: Theme.of(context).colorScheme.onPrimary, - fontWeight: isSelected ? FontWeight.w600 : FontWeight.w400, - ), - ), - ], - ), - ), - ); - } -} - -class _CodeCard extends StatelessWidget { - final String code; - - const _CodeCard({required this.code}); - - @override - Widget build(BuildContext context) { - final lines = code.split('\n'); - final lineNumberColor = Colors.white.withValues(alpha: 0.4); - - return SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Padding( - padding: const EdgeInsets.only(left: 12, top: 8, bottom: 8), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.end, - children: List.generate( - lines.length, - (index) => SizedBox( - height: 20, - child: Text( - '${index + 1}', - style: TextStyle( - fontFamily: 'GoogleSansCode', - fontSize: 13, - color: lineNumberColor, - height: 1.5, - ), - ), - ), - ), - ), - const SizedBox(width: 16), - SelectableText.rich( - _codeHighlighter.highlight(code), - style: const TextStyle( - fontFamily: 'GoogleSansCode', - fontSize: 13, - height: 1.54, - ), - ), - ], - ), - ), - ); - } -} diff --git a/mobile/packages/ui/showcase/lib/widgets/page_title.dart b/mobile/packages/ui/showcase/lib/widgets/page_title.dart deleted file mode 100644 index eae3bf6ffb..0000000000 --- a/mobile/packages/ui/showcase/lib/widgets/page_title.dart +++ /dev/null @@ -1,17 +0,0 @@ -import 'package:flutter/material.dart'; - -class PageTitle extends StatelessWidget { - final String title; - final Widget child; - - const PageTitle({super.key, required this.title, required this.child}); - - @override - Widget build(BuildContext context) { - return Title( - title: '$title | @immich/ui', - color: Theme.of(context).colorScheme.primary, - child: child, - ); - } -} diff --git a/mobile/packages/ui/showcase/lib/widgets/shell_layout.dart b/mobile/packages/ui/showcase/lib/widgets/shell_layout.dart deleted file mode 100644 index 8bcb687e75..0000000000 --- a/mobile/packages/ui/showcase/lib/widgets/shell_layout.dart +++ /dev/null @@ -1,59 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:showcase/constants.dart'; -import 'package:showcase/widgets/sidebar_navigation.dart'; - -class ShellLayout extends StatelessWidget { - final Widget child; - final VoidCallback onThemeToggle; - - const ShellLayout({ - super.key, - required this.child, - required this.onThemeToggle, - }); - - @override - Widget build(BuildContext context) { - final isDark = Theme.of(context).brightness == Brightness.dark; - - return Scaffold( - appBar: AppBar( - title: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Image.asset('assets/immich_logo.png', height: 32, width: 32), - const SizedBox(width: 8), - Image.asset( - isDark - ? 'assets/immich-text-dark.png' - : 'assets/immich-text-light.png', - height: 24, - filterQuality: FilterQuality.none, - isAntiAlias: true, - ), - ], - ), - actions: [ - IconButton( - icon: Icon( - isDark ? Icons.light_mode_outlined : Icons.dark_mode_outlined, - size: LayoutConstants.iconSizeLarge, - ), - onPressed: onThemeToggle, - tooltip: 'Toggle theme', - ), - ], - shape: Border( - bottom: BorderSide(color: Theme.of(context).dividerColor, width: 1), - ), - ), - body: Row( - children: [ - const SidebarNavigation(), - const VerticalDivider(), - Expanded(child: child), - ], - ), - ); - } -} diff --git a/mobile/packages/ui/showcase/lib/widgets/sidebar_navigation.dart b/mobile/packages/ui/showcase/lib/widgets/sidebar_navigation.dart deleted file mode 100644 index 10eba170e6..0000000000 --- a/mobile/packages/ui/showcase/lib/widgets/sidebar_navigation.dart +++ /dev/null @@ -1,117 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; -import 'package:showcase/constants.dart'; -import 'package:showcase/routes.dart'; - -class SidebarNavigation extends StatelessWidget { - const SidebarNavigation({super.key}); - - @override - Widget build(BuildContext context) { - return Container( - width: LayoutConstants.sidebarWidth, - decoration: BoxDecoration(color: Theme.of(context).colorScheme.surface), - child: ListView( - padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 16), - children: [ - ...routesByCategory.entries.expand((entry) { - final category = entry.key; - final routes = entry.value; - return [ - if (category != AppRouteCategory.root) _CategoryHeader(category), - ...routes.map((route) => _NavItem(route)), - const SizedBox(height: 24), - ]; - }), - ], - ), - ); - } -} - -class _CategoryHeader extends StatelessWidget { - final AppRouteCategory category; - - const _CategoryHeader(this.category); - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.only(left: 12, top: 8, bottom: 8), - child: Text( - category.displayName, - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: Theme.of(context).colorScheme.onSurfaceVariant, - fontWeight: FontWeight.w600, - letterSpacing: 0.5, - ), - ), - ); - } -} - -class _NavItem extends StatelessWidget { - final AppRoute route; - - const _NavItem(this.route); - - @override - Widget build(BuildContext context) { - final currentRoute = GoRouterState.of(context).uri.toString(); - final isSelected = currentRoute == route.path; - final isDark = Theme.of(context).brightness == Brightness.dark; - - return Padding( - padding: const EdgeInsets.symmetric(vertical: 4), - child: Material( - color: Colors.transparent, - child: InkWell( - onTap: () { - context.go(route.path); - }, - borderRadius: BorderRadius.circular( - LayoutConstants.borderRadiusMedium, - ), - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), - decoration: BoxDecoration( - color: isSelected - ? (isDark - ? Colors.white.withValues(alpha: 0.1) - : Theme.of( - context, - ).colorScheme.primaryContainer.withValues(alpha: 0.5)) - : Colors.transparent, - borderRadius: BorderRadius.circular( - LayoutConstants.borderRadiusMedium, - ), - ), - child: Row( - children: [ - Icon( - route.icon, - size: 20, - color: isSelected - ? Theme.of(context).colorScheme.primary - : Theme.of(context).colorScheme.onSurfaceVariant, - ), - const SizedBox(width: 16), - Expanded( - child: Text( - route.name, - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: isSelected - ? Theme.of(context).colorScheme.primary - : Theme.of(context).colorScheme.onSurface, - fontWeight: FontWeight.w500, - ), - ), - ), - ], - ), - ), - ), - ), - ); - } -} diff --git a/mobile/packages/ui/showcase/pubspec.lock b/mobile/packages/ui/showcase/pubspec.lock deleted file mode 100644 index c676b23c53..0000000000 --- a/mobile/packages/ui/showcase/pubspec.lock +++ /dev/null @@ -1,377 +0,0 @@ -# Generated by pub -# See https://dart.dev/tools/pub/glossary#lockfile -packages: - async: - dependency: transitive - description: - name: async - sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" - url: "https://pub.dev" - source: hosted - version: "2.13.0" - boolean_selector: - dependency: transitive - description: - name: boolean_selector - sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" - url: "https://pub.dev" - source: hosted - version: "2.1.2" - characters: - dependency: transitive - description: - name: characters - sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b - url: "https://pub.dev" - source: hosted - version: "1.4.1" - clock: - dependency: transitive - description: - name: clock - sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b - url: "https://pub.dev" - source: hosted - version: "1.1.2" - collection: - dependency: transitive - description: - name: collection - sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" - url: "https://pub.dev" - source: hosted - version: "1.19.1" - crypto: - dependency: transitive - description: - name: crypto - sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf - url: "https://pub.dev" - source: hosted - version: "3.0.7" - device_info_plus: - dependency: transitive - description: - name: device_info_plus - sha256: "98f28b42168cc509abc92f88518882fd58061ea372d7999aecc424345c7bff6a" - url: "https://pub.dev" - source: hosted - version: "11.5.0" - device_info_plus_platform_interface: - dependency: transitive - description: - name: device_info_plus_platform_interface - sha256: e1ea89119e34903dca74b883d0dd78eb762814f97fb6c76f35e9ff74d261a18f - url: "https://pub.dev" - source: hosted - version: "7.0.3" - fake_async: - dependency: transitive - description: - name: fake_async - sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" - url: "https://pub.dev" - source: hosted - version: "1.3.3" - ffi: - dependency: transitive - description: - name: ffi - sha256: d07d37192dbf97461359c1518788f203b0c9102cfd2c35a716b823741219542c - url: "https://pub.dev" - source: hosted - version: "2.1.5" - file: - dependency: transitive - description: - name: file - sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 - url: "https://pub.dev" - source: hosted - version: "7.0.1" - fixnum: - dependency: transitive - description: - name: fixnum - sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be - url: "https://pub.dev" - source: hosted - version: "1.1.1" - flutter: - dependency: "direct main" - description: flutter - source: sdk - version: "0.0.0" - flutter_lints: - dependency: "direct dev" - description: - name: flutter_lints - sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1" - url: "https://pub.dev" - source: hosted - version: "6.0.0" - flutter_test: - dependency: "direct dev" - description: flutter - source: sdk - version: "0.0.0" - flutter_web_plugins: - dependency: transitive - description: flutter - source: sdk - version: "0.0.0" - go_router: - dependency: "direct main" - description: - name: go_router - sha256: "5540e4a3f416dd4a93458257b908eb88353cbd0fb5b0a3d1bd7d849ba1e88735" - url: "https://pub.dev" - source: hosted - version: "17.2.1" - immich_ui: - dependency: "direct main" - description: - path: ".." - relative: true - source: path - version: "0.0.0" - irondash_engine_context: - dependency: transitive - description: - name: irondash_engine_context - sha256: "2bb0bc13dfda9f5aaef8dde06ecc5feb1379f5bb387d59716d799554f3f305d7" - url: "https://pub.dev" - source: hosted - version: "0.5.5" - irondash_message_channel: - dependency: transitive - description: - name: irondash_message_channel - sha256: b4101669776509c76133b8917ab8cfc704d3ad92a8c450b92934dd8884a2f060 - url: "https://pub.dev" - source: hosted - version: "0.7.0" - leak_tracker: - dependency: transitive - description: - name: leak_tracker - sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" - url: "https://pub.dev" - source: hosted - version: "11.0.2" - leak_tracker_flutter_testing: - dependency: transitive - description: - name: leak_tracker_flutter_testing - sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" - url: "https://pub.dev" - source: hosted - version: "3.0.10" - leak_tracker_testing: - dependency: transitive - description: - name: leak_tracker_testing - sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" - url: "https://pub.dev" - source: hosted - version: "3.0.2" - lints: - dependency: transitive - description: - name: lints - sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df" - url: "https://pub.dev" - source: hosted - version: "6.1.0" - logging: - dependency: transitive - description: - name: logging - sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 - url: "https://pub.dev" - source: hosted - version: "1.3.0" - matcher: - dependency: transitive - description: - name: matcher - sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 - url: "https://pub.dev" - source: hosted - version: "0.12.19" - material_color_utilities: - dependency: transitive - description: - name: material_color_utilities - sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" - url: "https://pub.dev" - source: hosted - version: "0.13.0" - meta: - dependency: transitive - description: - name: meta - sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" - url: "https://pub.dev" - source: hosted - version: "1.17.0" - path: - dependency: transitive - description: - name: path - sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" - url: "https://pub.dev" - source: hosted - version: "1.9.1" - pixel_snap: - dependency: transitive - description: - name: pixel_snap - sha256: "677410ea37b07cd37ecb6d5e6c0d8d7615a7cf3bd92ba406fd1ac57e937d1fb0" - url: "https://pub.dev" - source: hosted - version: "0.1.5" - plugin_platform_interface: - dependency: transitive - description: - name: plugin_platform_interface - sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" - url: "https://pub.dev" - source: hosted - version: "2.1.8" - sky_engine: - dependency: transitive - description: flutter - source: sdk - version: "0.0.0" - source_span: - dependency: transitive - description: - name: source_span - sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" - url: "https://pub.dev" - source: hosted - version: "1.10.1" - stack_trace: - dependency: transitive - description: - name: stack_trace - sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" - url: "https://pub.dev" - source: hosted - version: "1.12.1" - stream_channel: - dependency: transitive - description: - name: stream_channel - sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" - url: "https://pub.dev" - source: hosted - version: "2.1.4" - string_scanner: - dependency: transitive - description: - name: string_scanner - sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" - url: "https://pub.dev" - source: hosted - version: "1.4.1" - super_clipboard: - dependency: transitive - description: - name: super_clipboard - sha256: e73f3bb7e66cc9260efa1dc507f979138e7e106c3521e2dda2d0311f6d728a16 - url: "https://pub.dev" - source: hosted - version: "0.9.1" - super_native_extensions: - dependency: transitive - description: - name: super_native_extensions - sha256: b9611dcb68f1047d6f3ef11af25e4e68a21b1a705bbcc3eb8cb4e9f5c3148569 - url: "https://pub.dev" - source: hosted - version: "0.9.1" - syntax_highlight: - dependency: "direct main" - description: - name: syntax_highlight - sha256: "4d3ba40658cadba6ba55d697f29f00b43538ebb6eb4a0ca0e895c568eaced138" - url: "https://pub.dev" - source: hosted - version: "0.5.0" - term_glyph: - dependency: transitive - description: - name: term_glyph - sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" - url: "https://pub.dev" - source: hosted - version: "1.2.2" - test_api: - dependency: transitive - description: - name: test_api - sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" - url: "https://pub.dev" - source: hosted - version: "0.7.10" - typed_data: - dependency: transitive - description: - name: typed_data - sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 - url: "https://pub.dev" - source: hosted - version: "1.4.0" - uuid: - dependency: transitive - description: - name: uuid - sha256: a11b666489b1954e01d992f3d601b1804a33937b5a8fe677bd26b8a9f96f96e8 - url: "https://pub.dev" - source: hosted - version: "4.5.2" - vector_math: - dependency: transitive - description: - name: vector_math - sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b - url: "https://pub.dev" - source: hosted - version: "2.2.0" - vm_service: - dependency: transitive - description: - name: vm_service - sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" - url: "https://pub.dev" - source: hosted - version: "15.0.2" - web: - dependency: transitive - description: - name: web - sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" - url: "https://pub.dev" - source: hosted - version: "1.1.1" - win32: - dependency: transitive - description: - name: win32 - sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e - url: "https://pub.dev" - source: hosted - version: "5.15.0" - win32_registry: - dependency: transitive - description: - name: win32_registry - sha256: "6f1b564492d0147b330dd794fee8f512cec4977957f310f9951b5f9d83618dae" - url: "https://pub.dev" - source: hosted - version: "2.1.0" -sdks: - dart: ">=3.11.0 <4.0.0" - flutter: ">=3.35.0" diff --git a/mobile/packages/ui/showcase/pubspec.yaml b/mobile/packages/ui/showcase/pubspec.yaml deleted file mode 100644 index 6353600ce3..0000000000 --- a/mobile/packages/ui/showcase/pubspec.yaml +++ /dev/null @@ -1,47 +0,0 @@ -name: showcase -publish_to: 'none' - -version: 1.0.0+1 - -environment: - sdk: ^3.11.0 - -dependencies: - flutter: - sdk: flutter - immich_ui: - path: ../ - go_router: ^17.2.1 - syntax_highlight: ^0.5.0 - -dev_dependencies: - flutter_test: - sdk: flutter - flutter_lints: ^6.0.0 - -flutter: - uses-material-design: true - assets: - - assets/ - - assets/themes/ - - lib/pages/components/examples/ - - fonts: - - family: GoogleSans - fonts: - - asset: ../../../fonts/GoogleSans/GoogleSans-Regular.ttf - - asset: ../../../fonts/GoogleSans/GoogleSans-Italic.ttf - style: italic - - asset: ../../../fonts/GoogleSans/GoogleSans-Medium.ttf - weight: 500 - - asset: ../../../fonts/GoogleSans/GoogleSans-SemiBold.ttf - weight: 600 - - asset: ../../../fonts/GoogleSans/GoogleSans-Bold.ttf - weight: 700 - - family: GoogleSansCode - fonts: - - asset: ../../../fonts/GoogleSansCode/GoogleSansCode-Regular.ttf - - asset: ../../../fonts/GoogleSansCode/GoogleSansCode-Medium.ttf - weight: 500 - - asset: ../../../fonts/GoogleSansCode/GoogleSansCode-SemiBold.ttf - weight: 600 \ No newline at end of file diff --git a/mobile/packages/ui/showcase/web/favicon.ico b/mobile/packages/ui/showcase/web/favicon.ico deleted file mode 100644 index 7ec34e9e53..0000000000 Binary files a/mobile/packages/ui/showcase/web/favicon.ico and /dev/null differ diff --git a/mobile/packages/ui/showcase/web/icons/Icon-maskable-192.png b/mobile/packages/ui/showcase/web/icons/Icon-maskable-192.png deleted file mode 100644 index 49fd3ae289..0000000000 Binary files a/mobile/packages/ui/showcase/web/icons/Icon-maskable-192.png and /dev/null differ diff --git a/mobile/packages/ui/showcase/web/icons/Icon-maskable-512.png b/mobile/packages/ui/showcase/web/icons/Icon-maskable-512.png deleted file mode 100644 index a7220554bc..0000000000 Binary files a/mobile/packages/ui/showcase/web/icons/Icon-maskable-512.png and /dev/null differ diff --git a/mobile/packages/ui/showcase/web/icons/apple-icon-180.png b/mobile/packages/ui/showcase/web/icons/apple-icon-180.png deleted file mode 100644 index 4e642631a3..0000000000 Binary files a/mobile/packages/ui/showcase/web/icons/apple-icon-180.png and /dev/null differ diff --git a/mobile/packages/ui/showcase/web/index.html b/mobile/packages/ui/showcase/web/index.html deleted file mode 100644 index abf42ad1fd..0000000000 --- a/mobile/packages/ui/showcase/web/index.html +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - - - - - - - - - - - @immich/ui - - - - - - diff --git a/mobile/packages/ui/showcase/web/manifest.json b/mobile/packages/ui/showcase/web/manifest.json deleted file mode 100644 index 25b44bd1ae..0000000000 --- a/mobile/packages/ui/showcase/web/manifest.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "name": "@immich/ui Showcase", - "short_name": "@immich/ui", - "start_url": ".", - "display": "standalone", - "background_color": "#FCFCFD", - "theme_color": "#4250AF", - "description": "Immich UI component library showcase and documentation", - "orientation": "landscape", - "prefer_related_applications": false, - "icons": [ - { - "src": "icons/Icon-maskable-192.png", - "sizes": "192x192", - "type": "image/png", - "purpose": "any" - }, - { - "src": "icons/Icon-maskable-192.png", - "sizes": "192x192", - "type": "image/png", - "purpose": "maskable" - }, - { - "src": "icons/Icon-maskable-512.png", - "sizes": "512x512", - "type": "image/png", - "purpose": "any" - }, - { - "src": "icons/Icon-maskable-512.png", - "sizes": "512x512", - "type": "image/png", - "purpose": "maskable" - } - ] -} diff --git a/mobile/packages/ui/test/formatted_text_test.dart b/mobile/packages/ui/test/formatted_text_test.dart index 54ef343727..c3901cd802 100644 --- a/mobile/packages/ui/test/formatted_text_test.dart +++ b/mobile/packages/ui/test/formatted_text_test.dart @@ -10,7 +10,9 @@ List _getContentSpans(WidgetTester tester) { final richText = tester.widget(find.byType(RichText)); final root = richText.text as TextSpan; final wrapper = root.children?.firstOrNull; - if (wrapper is TextSpan) return wrapper.children ?? []; + if (wrapper is TextSpan) { + return wrapper.children ?? []; + } return []; } diff --git a/mobile/pigeon/background_worker_api.dart b/mobile/pigeon/background_worker_api.dart index a40d290199..06395fae7b 100644 --- a/mobile/pigeon/background_worker_api.dart +++ b/mobile/pigeon/background_worker_api.dart @@ -47,7 +47,7 @@ abstract class BackgroundWorkerFlutterApi { // Android Only: Called when the Android background upload is triggered @async - void onAndroidUpload(); + void onAndroidUpload(int? maxMinutes); @async void cancel(); diff --git a/mobile/pigeon/local_image_api.dart b/mobile/pigeon/local_image_api.dart index eb538d7b1a..46643b7956 100644 --- a/mobile/pigeon/local_image_api.dart +++ b/mobile/pigeon/local_image_api.dart @@ -5,8 +5,7 @@ import 'package:pigeon/pigeon.dart'; dartOut: 'lib/platform/local_image_api.g.dart', swiftOut: 'ios/Runner/Images/LocalImages.g.swift', swiftOptions: SwiftOptions(includeErrorClass: false), - kotlinOut: - 'android/app/src/main/kotlin/app/alextran/immich/images/LocalImages.g.kt', + kotlinOut: 'android/app/src/main/kotlin/app/alextran/immich/images/LocalImages.g.kt', kotlinOptions: KotlinOptions(package: 'app.alextran.immich.images'), dartOptions: DartOptions(), dartPackageName: 'immich_mobile', diff --git a/mobile/pigeon/native_sync_api.dart b/mobile/pigeon/native_sync_api.dart index e215ab9447..433b154cd1 100644 --- a/mobile/pigeon/native_sync_api.dart +++ b/mobile/pigeon/native_sync_api.dart @@ -11,14 +11,7 @@ import 'package:pigeon/pigeon.dart'; dartPackageName: 'immich_mobile', ), ) -enum PlatformAssetPlaybackStyle { - unknown, - image, - video, - imageAnimated, - livePhoto, - videoLooping, -} +enum PlatformAssetPlaybackStyle { unknown, image, video, imageAnimated, livePhoto, videoLooping } class PlatformAsset { final String id; @@ -112,25 +105,26 @@ class CloudIdResult { @HostApi() abstract class NativeSyncApi { + @async bool shouldFullSync(); - @TaskQueue(type: TaskQueueType.serialBackgroundThread) + @async SyncDelta getMediaChanges(); void checkpointSync(); void clearSyncCheckpoint(); - @TaskQueue(type: TaskQueueType.serialBackgroundThread) + @async List getAssetIdsForAlbum(String albumId); - @TaskQueue(type: TaskQueueType.serialBackgroundThread) + @async List getAlbums(); @TaskQueue(type: TaskQueueType.serialBackgroundThread) int getAssetsCountSince(String albumId, int timestamp); - @TaskQueue(type: TaskQueueType.serialBackgroundThread) + @async List getAssetsForAlbum(String albumId, {int? updatedTimeCond}); @async @@ -139,9 +133,14 @@ abstract class NativeSyncApi { void cancelHashing(); + void cancelSync(); + @TaskQueue(type: TaskQueueType.serialBackgroundThread) Map> getTrashedAssets(); + @async + bool restoreFromTrashById(String mediaId, int type); + @TaskQueue(type: TaskQueueType.serialBackgroundThread) List getCloudIdForAssetIds(List assetIds); } diff --git a/mobile/pigeon/network_api.dart b/mobile/pigeon/network_api.dart index 704efed770..a701995342 100644 --- a/mobile/pigeon/network_api.dart +++ b/mobile/pigeon/network_api.dart @@ -44,4 +44,6 @@ abstract class NetworkApi { int getClientPointer(); void setRequestHeaders(Map headers, List serverUrls, String? token); + + String getAppGroupId(); } diff --git a/mobile/pigeon/permission_api.dart b/mobile/pigeon/permission_api.dart new file mode 100644 index 0000000000..a924f32e27 --- /dev/null +++ b/mobile/pigeon/permission_api.dart @@ -0,0 +1,23 @@ +import 'package:pigeon/pigeon.dart'; + +@ConfigurePigeon( + PigeonOptions( + dartOut: 'lib/platform/permission_api.g.dart', + swiftOut: 'ios/Runner/Permission/PermissionApi.g.swift', + swiftOptions: SwiftOptions(), + kotlinOut: 'android/app/src/main/kotlin/app/alextran/immich/permission/PermissionApi.g.kt', + kotlinOptions: KotlinOptions(package: 'app.alextran.immich.permission'), + dartOptions: DartOptions(), + dartPackageName: 'immich_mobile', + ), +) +@HostApi() +abstract class PermissionApi { + bool hasManageMediaPermission(); + + @async + bool requestManageMediaPermission(); + + @async + bool manageMediaPermission(); +} diff --git a/mobile/pigeon/view_intent_api.dart b/mobile/pigeon/view_intent_api.dart new file mode 100644 index 0000000000..f6a5162fef --- /dev/null +++ b/mobile/pigeon/view_intent_api.dart @@ -0,0 +1,24 @@ +import 'package:pigeon/pigeon.dart'; + +@ConfigurePigeon( + PigeonOptions( + dartOut: 'lib/platform/view_intent_api.g.dart', + kotlinOut: 'android/app/src/main/kotlin/app/alextran/immich/viewintent/ViewIntent.g.kt', + kotlinOptions: KotlinOptions(package: 'app.alextran.immich.viewintent'), + dartOptions: DartOptions(), + dartPackageName: 'immich_mobile', + ), +) +class ViewIntentPayload { + final String? path; + final String mimeType; + final String? localAssetId; + + const ViewIntentPayload({this.path, required this.mimeType, this.localAssetId}); +} + +@HostApi() +abstract class ViewIntentHostApi { + @async + ViewIntentPayload? consumeViewIntent(); +} diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index 5f7aa3a928..72bdc6b298 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -5,18 +5,18 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "8d7ff3948166b8ec5da0fbb5962000926b8e02f2ed9b3e51d1738905fbd4c98d" + sha256: "3b19a47f6ea7c2632760777c78174f47f6aec1e05f0cd611380d4593b8af1dbc" url: "https://pub.dev" source: hosted - version: "93.0.0" + version: "96.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: de7148ed2fcec579b19f122c1800933dfa028f6d9fd38a152b04b1516cec120b + sha256: "0c516bc4ad36a1a75759e54d5047cb9d15cded4459df01aa35a0b5ec7db2c2a0" url: "https://pub.dev" source: hosted - version: "10.0.1" + version: "10.2.0" ansicolor: dependency: transitive description: @@ -133,10 +133,10 @@ packages: dependency: transitive description: name: build - sha256: aadd943f4f8cc946882c954c187e6115a84c98c81ad1d9c6cbf0895a8c85da9c + sha256: a156715e7cd728130c592f30552575908aae5b100005fbc1f0fb16b3c03a3d10 url: "https://pub.dev" source: hosted - version: "4.0.5" + version: "4.0.6" build_config: dependency: transitive description: @@ -157,10 +157,10 @@ packages: dependency: "direct dev" description: name: build_runner - sha256: "521daf8d189deb79ba474e43a696b41c49fb3987818dbacf3308f1e03673a75e" + sha256: "1523ce62448ebac2c15a8ba5fbad8acac169788658a7dd2a1c2d9c2a9318b9a6" url: "https://pub.dev" source: hosted - version: "2.13.1" + version: "2.15.0" built_collection: dependency: transitive description: @@ -173,10 +173,10 @@ packages: dependency: transitive description: name: built_value - sha256: "0730c18c770d05636a8f945c32a4d7d81cb6e0f0148c8db4ad12e7748f7e49af" + sha256: "34e4067d30ce212937df995f03b69992eea683539ceeac7f679a1f1eba055b56" url: "https://pub.dev" source: hosted - version: "8.12.5" + version: "8.12.6" cast: dependency: "direct main" description: @@ -253,10 +253,10 @@ packages: dependency: "direct main" description: name: connectivity_plus - sha256: b5e72753cf63becce2c61fd04dfe0f1c430cc5278b53a1342dc5ad839eab29ec + sha256: "62ffa266d9a23b79fb3fcbc206afc00bb979417ba57b1324c546b5aab95ba057" url: "https://pub.dev" source: hosted - version: "6.1.5" + version: "7.1.1" connectivity_plus_platform_interface: dependency: transitive description: @@ -358,18 +358,18 @@ packages: dependency: "direct main" description: name: drift - sha256: "055c249d1f91be5a47fe447f88afc24c4ca6f4cd6c5ed66767b4797d48acc2e5" + sha256: "8033500116b24398fba0cca0369cc31678cd627c01e41753a61186911cea743e" url: "https://pub.dev" source: hosted - version: "2.32.1" + version: "2.33.0" drift_dev: dependency: "direct dev" description: name: drift_dev - sha256: "88a9de3af8571518148a6d8a513b57779fd1e60a026d3ab8a481a878fba01d91" + sha256: b3dd5b75e30522a91da8abda9f5bb17230cb038097f6d15fa75d42bb563428aa url: "https://pub.dev" source: hosted - version: "2.32.1" + version: "2.33.0" drift_flutter: dependency: "direct main" description: @@ -613,10 +613,10 @@ packages: dependency: "direct main" description: name: flutter_svg - sha256: "1ded017b39c8e15c8948ea855070a5ff8ff8b3d5e83f3446e02d6bb12add7ad9" + sha256: "35882981abcbfb8c15b286f0cd690ff25bac12d95eff3e25ee207f37d4c42e7f" url: "https://pub.dev" source: hosted - version: "2.2.4" + version: "2.3.0" flutter_test: dependency: "direct dev" description: flutter @@ -772,10 +772,10 @@ packages: dependency: transitive description: name: hooks - sha256: e79ed1e8e1929bc6ecb6ec85f0cb519c887aa5b423705ded0d0f2d9226def388 + sha256: "025f060e86d2d4c3c47b56e33caf7f93bf9283340f26d23424ebcfccf34f621e" url: "https://pub.dev" source: hosted - version: "1.0.2" + version: "1.0.3" hooks_riverpod: dependency: "direct main" description: @@ -844,18 +844,18 @@ packages: dependency: "direct main" description: name: image_picker - sha256: "784210112be18ea55f69d7076e2c656a4e24949fa9e76429fe53af0c0f4fa320" + sha256: "91c025426c2881c551100bce834e201c835a170151545f58d17da5180ca7d9ac" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" image_picker_android: dependency: transitive description: name: image_picker_android - sha256: "66810af8e99b2657ee98e5c6f02064f69bb63f7a70e343937f70946c5f8c6622" + sha256: d5b3e1774af29c9ab00103afb0d4614070f924d2e0057ac867ec98800114793f url: "https://pub.dev" source: hosted - version: "0.8.13+16" + version: "0.8.13+17" image_picker_for_web: dependency: transitive description: @@ -952,10 +952,10 @@ packages: dependency: transitive description: name: json_annotation - sha256: cb09e7dac6210041fad964ed7fbee004f14258b4eca4040f72d1234062ace4c8 + sha256: "2a743920d81b7910627f68ee2c9ac1fc0bfee32b9fc3403587d7c6791ca12f80" url: "https://pub.dev" source: hosted - version: "4.11.0" + version: "4.12.0" leak_tracker: dependency: transitive description: @@ -984,10 +984,10 @@ packages: dependency: transitive description: name: lean_builder - sha256: ee4117b03e93a4eb83e1a78c8e7a1dc22188d43bb142309982be48673a1b3a53 + sha256: c16e95ddf7b2d49dd551357b7212fe2ce9f13ec7ad1b1e660c157184031e96c0 url: "https://pub.dev" source: hosted - version: "0.1.7" + version: "0.1.10" lints: dependency: transitive description: @@ -1088,10 +1088,10 @@ packages: dependency: transitive description: name: meta - sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.18.0" mime: dependency: transitive description: @@ -1429,6 +1429,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.0" + record_use: + dependency: transitive + description: + name: record_use + sha256: "2551bd8eecfe95d14ae75f6021ad0248be5c27f138c2ec12fcb52b500b3ba1ed" + url: "https://pub.dev" + source: hosted + version: "0.6.0" riverpod: dependency: transitive description: @@ -1607,10 +1615,10 @@ packages: dependency: transitive description: name: source_gen - sha256: "732792cfd197d2161a65bb029606a46e0a18ff30ef9e141a7a82172b05ea8ecd" + sha256: ec37cc0e6694374cbef59ed79685572c870a54ede6fa30a3e420feb3adffea02 url: "https://pub.dev" source: hosted - version: "4.2.2" + version: "4.2.3" source_span: dependency: transitive description: @@ -1647,10 +1655,10 @@ packages: dependency: transitive description: name: sqlparser - sha256: ab2b467425f1d4f3acfa5fd11a08226f7d6c26ff102c06be1807e1dff34e050b + sha256: ecdc06d4a7d79dcbc928d99afd2f7f5b0f98a637c46f89be83d911617f759978 url: "https://pub.dev" source: hosted - version: "0.44.3" + version: "0.44.4" stack_trace: dependency: transitive description: @@ -1711,10 +1719,10 @@ packages: dependency: transitive description: name: test_api - sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" + sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" url: "https://pub.dev" source: hosted - version: "0.7.10" + version: "0.7.11" thumbhash: dependency: "direct main" description: @@ -1767,10 +1775,10 @@ packages: dependency: transitive description: name: url_launcher_android - sha256: "3bb000251e55d4a209aa0e2e563309dc9bb2befea2295fd0cec1f51760aac572" + sha256: "17bc677f0b301615530dd1d67e0a9828cafa2d0b6b6eae4cd3679b7eac4a273c" url: "https://pub.dev" source: hosted - version: "6.3.29" + version: "6.3.30" url_launcher_ios: dependency: transitive description: @@ -1807,10 +1815,10 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: d0412fcf4c6b31ecfdb7762359b7206ffba3bbffd396c6d9f9c4616ece476c1f + sha256: "85c81589622fbc87c1c683aaea164d3604a7777495a79d91e39ffcdec39ddb34" url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.4.3" url_launcher_windows: dependency: transitive description: @@ -1831,10 +1839,10 @@ packages: dependency: transitive description: name: vector_graphics - sha256: "81da85e9ca8885ade47f9685b953cb098970d11be4821ac765580a6607ea4373" + sha256: "2306c03da2ba81724afeb589c351ebbc0aa7d86005925be8f8735856dbe5e42d" url: "https://pub.dev" source: hosted - version: "1.1.21" + version: "1.2.2" vector_graphics_codec: dependency: transitive description: @@ -1847,10 +1855,10 @@ packages: dependency: transitive description: name: vector_graphics_compiler - sha256: "5a88dd14c0954a5398af544651c7fb51b457a2a556949bfb25369b210ef73a74" + sha256: b9b3f391857781aa96acacef96066f2f49b4cd03cf9fce3ca4d8da2ef5ea129e url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.2.3" vector_math: dependency: transitive description: @@ -1863,10 +1871,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "046d3928e16fa4dc46e8350415661755ab759d9fc97fc21b5ab295f71e4f0499" + sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" url: "https://pub.dev" source: hosted - version: "15.1.0" + version: "15.2.0" wakelock_plus: dependency: "direct main" description: @@ -1879,10 +1887,10 @@ packages: dependency: transitive description: name: wakelock_plus_platform_interface - sha256: "14b2e5b9e35c2631e656913c47adecdd71633ae92896a27a64c8f1fcfabc21cc" + sha256: b13f99e992e7ae6a152e16c5559d3c07ff445b13330192662494e614ca3e7d7b url: "https://pub.dev" source: hosted - version: "1.5.0" + version: "1.5.1" watcher: dependency: transitive description: @@ -1988,5 +1996,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.11.0 <4.0.0" - flutter: "3.41.9" + dart: ">=3.12.0 <4.0.0" + flutter: "3.44.1" diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index 8cb7eb17eb..206f9ac2b3 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -2,11 +2,11 @@ name: immich_mobile description: Immich - selfhosted backup media file on mobile phone publish_to: 'none' -version: 2.7.5+3046 +version: 3.0.0+3047 environment: - sdk: '>=3.11.0 <4.0.0' - flutter: 3.41.9 + sdk: '>=3.12.0 <4.0.0' + flutter: 3.44.1 dependencies: async: ^2.13.1 @@ -14,7 +14,7 @@ dependencies: background_downloader: ^9.5.4 cast: ^2.1.0 collection: ^1.19.1 - connectivity_plus: ^6.1.5 + connectivity_plus: ^7.0.0 crop_image: ^1.0.17 crypto: ^3.0.7 device_info_plus: ^12.4.0 diff --git a/mobile/test/domain/repositories/sync_stream_repository_test.dart b/mobile/test/domain/repositories/sync_stream_repository_test.dart index a26683213c..bd47f63da5 100644 --- a/mobile/test/domain/repositories/sync_stream_repository_test.dart +++ b/mobile/test/domain/repositories/sync_stream_repository_test.dart @@ -1,6 +1,10 @@ import 'package:drift/drift.dart' as drift; import 'package:drift/native.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:immich_mobile/domain/models/album/album.model.dart'; +import 'package:immich_mobile/domain/models/album/local_album.model.dart'; +import 'package:immich_mobile/infrastructure/entities/local_album.entity.drift.dart'; +import 'package:immich_mobile/infrastructure/entities/remote_album.entity.drift.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/sync_stream.repository.dart'; import 'package:openapi/api.dart'; @@ -11,7 +15,7 @@ SyncUserV1 _createUser({String id = 'user-1'}) { name: 'Test User', email: 'test@test.com', deletedAt: null, - avatarColor: null, + avatarColor: const Optional.absent(), hasProfileImage: false, profileChangedAt: DateTime(2024, 1, 1), ); @@ -34,6 +38,7 @@ SyncAssetV1 _createAsset({ isFavorite: false, fileCreatedAt: DateTime(2024, 1, 1), fileModifiedAt: DateTime(2024, 1, 1), + createdAt: DateTime(2024, 1, 1), localDateTime: DateTime(2024, 1, 1), visibility: AssetVisibility.timeline, width: width, @@ -183,4 +188,56 @@ void main() { expect(result.height, equals(existingHeight), reason: 'Height should remain as originally set'); }); }); + + group('SyncStreamRepository - reset()', () { + test('nulls linkedRemoteAlbumId on localAlbumEntity so FK refs do not dangle', () async { + const localAlbumId = 'local-1'; + const remoteAlbumId = 'remote-1'; + + await db.remoteAlbumEntity.insertOne( + RemoteAlbumEntityCompanion.insert(id: remoteAlbumId, name: 'Movies', order: AlbumAssetOrder.desc), + ); + await db.localAlbumEntity.insertOne( + LocalAlbumEntityCompanion.insert( + id: localAlbumId, + name: 'Movies', + backupSelection: BackupSelection.selected, + linkedRemoteAlbumId: const drift.Value(remoteAlbumId), + ), + ); + + // sanity: link is set before reset + final before = await (db.localAlbumEntity.select()..where((t) => t.id.equals(localAlbumId))).getSingle(); + expect(before.linkedRemoteAlbumId, equals(remoteAlbumId)); + + await sut.reset(); + + final after = await (db.localAlbumEntity.select()..where((t) => t.id.equals(localAlbumId))).getSingle(); + expect( + after.linkedRemoteAlbumId, + isNull, + reason: + 'reset() runs with PRAGMA foreign_keys = OFF so the ON DELETE SET NULL cascade does not fire โ€” the link must be nulled manually', + ); + expect(after.name, equals('Movies'), reason: 'local album row itself must be preserved'); + expect(after.backupSelection, equals(BackupSelection.selected)); + + final remoteRows = await db.remoteAlbumEntity.select().get(); + expect(remoteRows, isEmpty, reason: 'reset() still wipes remoteAlbumEntity'); + }); + + test('preserves localAlbumEntity rows that have no linkedRemoteAlbumId', () async { + const localAlbumId = 'local-unlinked'; + await db.localAlbumEntity.insertOne( + LocalAlbumEntityCompanion.insert(id: localAlbumId, name: 'Camera', backupSelection: BackupSelection.none), + ); + + await sut.reset(); + + final after = await (db.localAlbumEntity.select()..where((t) => t.id.equals(localAlbumId))).getSingle(); + expect(after.linkedRemoteAlbumId, isNull); + expect(after.name, equals('Camera')); + expect(after.backupSelection, equals(BackupSelection.none)); + }); + }); } diff --git a/mobile/test/domain/services/local_sync_service_test.dart b/mobile/test/domain/services/local_sync_service_test.dart index c15bf9b2d0..e0e6b663a7 100644 --- a/mobile/test/domain/services/local_sync_service_test.dart +++ b/mobile/test/domain/services/local_sync_service_test.dart @@ -10,17 +10,15 @@ import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/store.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/trashed_local_asset.repository.dart'; import 'package:immich_mobile/platform/native_sync_api.g.dart'; -import 'package:immich_mobile/repositories/local_files_manager.repository.dart'; +import 'package:immich_mobile/repositories/asset_media.repository.dart'; import 'package:mocktail/mocktail.dart'; import '../../domain/service.mock.dart'; import '../../fixtures/asset.stub.dart'; import '../../infrastructure/repository.mock.dart'; -import '../../mocks/asset_entity.mock.dart'; import '../../repository.mocks.dart'; void main() { @@ -28,8 +26,8 @@ void main() { late DriftLocalAlbumRepository mockLocalAlbumRepository; late DriftLocalAssetRepository mockLocalAssetRepository; late DriftTrashedLocalAssetRepository mockTrashedLocalAssetRepository; - late LocalFilesManagerRepository mockLocalFilesManager; - late StorageRepository mockStorageRepository; + late AssetMediaRepository mockAssetMediaRepository; + late MockPermissionRepository mockPermissionRepository; late MockNativeSyncApi mockNativeSyncApi; late Drift db; @@ -51,8 +49,8 @@ void main() { mockLocalAlbumRepository = MockLocalAlbumRepository(); mockLocalAssetRepository = MockLocalAssetRepository(); mockTrashedLocalAssetRepository = MockTrashedLocalAssetRepository(); - mockLocalFilesManager = MockLocalFilesManagerRepository(); - mockStorageRepository = MockStorageRepository(); + mockAssetMediaRepository = MockAssetMediaRepository(); + mockPermissionRepository = MockPermissionRepository(); mockNativeSyncApi = MockNativeSyncApi(); when(() => mockNativeSyncApi.shouldFullSync()).thenAnswer((_) async => false); @@ -65,25 +63,28 @@ void main() { when(() => mockTrashedLocalAssetRepository.getToTrash()).thenAnswer((_) async => {}); when(() => mockTrashedLocalAssetRepository.applyRestoredAssets(any())).thenAnswer((_) async {}); when(() => mockTrashedLocalAssetRepository.trashLocalAsset(any())).thenAnswer((_) async {}); - when(() => mockLocalFilesManager.moveToTrash(any>())).thenAnswer((_) async => true); + when(() => mockAssetMediaRepository.deleteAll(any())).thenAnswer((invocation) async { + final ids = invocation.positionalArguments.first as List; + return ids; + }); sut = LocalSyncService( localAlbumRepository: mockLocalAlbumRepository, localAssetRepository: mockLocalAssetRepository, trashedLocalAssetRepository: mockTrashedLocalAssetRepository, - localFilesManager: mockLocalFilesManager, - storageRepository: mockStorageRepository, + assetMediaRepository: mockAssetMediaRepository, + permissionRepository: mockPermissionRepository, nativeSyncApi: mockNativeSyncApi, ); await Store.put(StoreKey.manageLocalMediaAndroid, false); - when(() => mockLocalFilesManager.hasManageMediaPermission()).thenAnswer((_) async => false); + when(() => mockPermissionRepository.hasManageMediaPermission()).thenAnswer((_) async => false); }); group('LocalSyncService - syncTrashedAssets gating', () { test('invokes syncTrashedAssets when Android flag enabled and permission granted', () async { await Store.put(StoreKey.manageLocalMediaAndroid, true); - when(() => mockLocalFilesManager.hasManageMediaPermission()).thenAnswer((_) async => true); + when(() => mockPermissionRepository.hasManageMediaPermission()).thenAnswer((_) async => true); await sut.sync(); @@ -93,7 +94,7 @@ void main() { test('skips syncTrashedAssets when store flag disabled', () async { await Store.put(StoreKey.manageLocalMediaAndroid, false); - when(() => mockLocalFilesManager.hasManageMediaPermission()).thenAnswer((_) async => true); + when(() => mockPermissionRepository.hasManageMediaPermission()).thenAnswer((_) async => true); await sut.sync(); @@ -102,7 +103,7 @@ void main() { test('skips syncTrashedAssets when MANAGE_MEDIA permission absent', () async { await Store.put(StoreKey.manageLocalMediaAndroid, true); - when(() => mockLocalFilesManager.hasManageMediaPermission()).thenAnswer((_) async => false); + when(() => mockPermissionRepository.hasManageMediaPermission()).thenAnswer((_) async => false); await sut.sync(); @@ -114,7 +115,7 @@ void main() { addTearDown(() => debugDefaultTargetPlatformOverride = TargetPlatform.android); await Store.put(StoreKey.manageLocalMediaAndroid, true); - when(() => mockLocalFilesManager.hasManageMediaPermission()).thenAnswer((_) async => true); + when(() => mockPermissionRepository.hasManageMediaPermission()).thenAnswer((_) async => true); await sut.sync(); @@ -131,13 +132,13 @@ void main() { durationMs: 0, orientation: 0, isFavorite: false, - playbackStyle: PlatformAssetPlaybackStyle.image + playbackStyle: PlatformAssetPlaybackStyle.image, ); final assetsToRestore = [LocalAssetStub.image1]; when(() => mockTrashedLocalAssetRepository.getToRestore()).thenAnswer((_) async => assetsToRestore); final restoredIds = ['image1']; - when(() => mockLocalFilesManager.restoreAssetsFromTrash(any())).thenAnswer((invocation) async { + when(() => mockAssetMediaRepository.restoreAssetsFromTrash(any())).thenAnswer((invocation) async { final Iterable requested = invocation.positionalArguments.first as Iterable; expect(requested, orderedEquals(assetsToRestore)); return restoredIds; @@ -150,10 +151,6 @@ void main() { }, ); - final assetEntity = MockAssetEntity(); - when(() => assetEntity.getMediaUrl()).thenAnswer((_) async => 'content://local-trash'); - when(() => mockStorageRepository.getAssetEntityForAsset(localAssetToTrash)).thenAnswer((_) async => assetEntity); - await sut.processTrashedAssets({ 'album-a': [platformAsset], }); @@ -168,12 +165,11 @@ void main() { expect(trashedEntry.asset.name, platformAsset.name); verify(() => mockTrashedLocalAssetRepository.getToTrash()).called(1); - verify(() => mockLocalFilesManager.restoreAssetsFromTrash(any())).called(1); + verify(() => mockAssetMediaRepository.restoreAssetsFromTrash(any())).called(1); verify(() => mockTrashedLocalAssetRepository.applyRestoredAssets(restoredIds)).called(1); - verify(() => mockStorageRepository.getAssetEntityForAsset(localAssetToTrash)).called(1); - final moveArgs = verify(() => mockLocalFilesManager.moveToTrash(captureAny())).captured.single as List; - expect(moveArgs, ['content://local-trash']); + final moveArgs = verify(() => mockAssetMediaRepository.deleteAll(captureAny())).captured.single as List; + expect(moveArgs, ['local-trash']); final trashArgs = verify(() => mockTrashedLocalAssetRepository.trashLocalAsset(captureAny())).captured.single as Map>; @@ -181,6 +177,26 @@ void main() { expect(trashArgs['album-a'], [localAssetToTrash]); }); + test('records only local assets that were moved to device trash', () async { + final movedAsset = LocalAssetStub.image1.copyWith(id: 'moved-local', checksum: 'checksum-moved'); + final skippedAsset = LocalAssetStub.image2.copyWith(id: 'skipped-local', checksum: 'checksum-skipped'); + when(() => mockTrashedLocalAssetRepository.getToTrash()).thenAnswer( + (_) async => { + 'album-a': [movedAsset], + 'album-b': [skippedAsset], + }, + ); + when(() => mockAssetMediaRepository.deleteAll(any())).thenAnswer((_) async => ['moved-local']); + + await sut.processTrashedAssets({}); + + final trashArgs = + verify(() => mockTrashedLocalAssetRepository.trashLocalAsset(captureAny())).captured.single + as Map>; + expect(trashArgs.keys, ['album-a']); + expect(trashArgs['album-a'], [movedAsset]); + }); + test('does not attempt restore when repository has no assets to restore', () async { when(() => mockTrashedLocalAssetRepository.getToRestore()).thenAnswer((_) async => []); @@ -190,7 +206,7 @@ void main() { verify(() => mockTrashedLocalAssetRepository.processTrashSnapshot(captureAny())).captured.single as Iterable; expect(trashedSnapshot, isEmpty); - verifyNever(() => mockLocalFilesManager.restoreAssetsFromTrash(any())); + verifyNever(() => mockAssetMediaRepository.restoreAssetsFromTrash(any())); verifyNever(() => mockTrashedLocalAssetRepository.applyRestoredAssets(any())); }); @@ -199,7 +215,7 @@ void main() { await sut.processTrashedAssets({}); - verifyNever(() => mockLocalFilesManager.moveToTrash(any())); + verifyNever(() => mockAssetMediaRepository.deleteAll(any())); verifyNever(() => mockTrashedLocalAssetRepository.trashLocalAsset(any())); }); }); @@ -215,7 +231,7 @@ void main() { isFavorite: false, createdAt: 1700000000, updatedAt: 1732000000, - playbackStyle: PlatformAssetPlaybackStyle.image + playbackStyle: PlatformAssetPlaybackStyle.image, ); final localAsset = platformAsset.toLocalAsset(); diff --git a/mobile/test/domain/services/log_service_test.dart b/mobile/test/domain/services/log_service_test.dart index 0ccef393ab..6a82d1dce3 100644 --- a/mobile/test/domain/services/log_service_test.dart +++ b/mobile/test/domain/services/log_service_test.dart @@ -1,11 +1,11 @@ import 'package:collection/collection.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:immich_mobile/constants/constants.dart'; +import 'package:immich_mobile/domain/models/config/app_config.dart'; import 'package:immich_mobile/domain/models/log.model.dart'; -import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/domain/models/settings_key.dart'; import 'package:immich_mobile/domain/services/log.service.dart'; import 'package:immich_mobile/infrastructure/repositories/log.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/store.repository.dart'; import 'package:logging/logging.dart'; import 'package:mocktail/mocktail.dart'; @@ -29,21 +29,23 @@ final _kWarnLog = LogMessage( void main() { late LogService sut; late LogRepository mockLogRepo; - late DriftStoreRepository mockStoreRepo; + late MockSettingsRepository mockSettingsRepository; setUp(() async { mockLogRepo = MockLogRepository(); - mockStoreRepo = MockDriftStoreRepository(); + mockSettingsRepository = MockSettingsRepository(); registerFallbackValue(_kInfoLog); + registerFallbackValue(LogLevel.info); when(() => mockLogRepo.truncate(limit: any(named: 'limit'))).thenAnswer((_) async => {}); - when(() => mockStoreRepo.tryGet(StoreKey.logLevel)).thenAnswer((_) async => LogLevel.fine.index); + when(() => mockSettingsRepository.appConfig).thenReturn(const AppConfig(logLevel: LogLevel.fine)); + when(() => mockSettingsRepository.write(SettingsKey.logLevel, any())).thenAnswer((_) async {}); when(() => mockLogRepo.getAll()).thenAnswer((_) async => []); when(() => mockLogRepo.insert(any())).thenAnswer((_) async => true); when(() => mockLogRepo.insertAll(any())).thenAnswer((_) async => true); - sut = await LogService.create(logRepository: mockLogRepo, storeRepository: mockStoreRepo); + sut = await LogService.create(logRepository: mockLogRepo, settingsRepository: mockSettingsRepository); }); tearDown(() async { @@ -56,21 +58,22 @@ void main() { expect(limit, kLogTruncateLimit); }); - test('Sets log level based on the store setting', () { - verify(() => mockStoreRepo.tryGet(StoreKey.logLevel)).called(1); + test('Sets log level based on the metadata repository', () { + verify(() => mockSettingsRepository.appConfig).called(1); expect(Logger.root.level, Level.FINE); }); }); group("Log Service Set Level:", () { setUp(() async { - when(() => mockStoreRepo.upsert(StoreKey.logLevel, any())).thenAnswer((_) async => true); await sut.setLogLevel(LogLevel.shout); }); - test('Updates the log level in store', () { - final index = verify(() => mockStoreRepo.upsert(StoreKey.logLevel, captureAny())).captured.firstOrNull; - expect(index, LogLevel.shout.index); + test('Updates the log level via metadata repository', () { + final captured = verify( + () => mockSettingsRepository.write(SettingsKey.logLevel, captureAny()), + ).captured.firstOrNull; + expect(captured, LogLevel.shout); }); test('Sets log level on logger', () { @@ -81,7 +84,11 @@ void main() { group("Log Service Buffer:", () { test('Buffers logs until timer elapses', () { TestUtils.fakeAsync((time) async { - sut = await LogService.create(logRepository: mockLogRepo, storeRepository: mockStoreRepo, shouldBuffer: true); + sut = await LogService.create( + logRepository: mockLogRepo, + settingsRepository: mockSettingsRepository, + shouldBuffer: true, + ); final logger = Logger(_kInfoLog.logger!); logger.info(_kInfoLog.message); @@ -95,7 +102,11 @@ void main() { test('Batch inserts all logs on timer', () { TestUtils.fakeAsync((time) async { - sut = await LogService.create(logRepository: mockLogRepo, storeRepository: mockStoreRepo, shouldBuffer: true); + sut = await LogService.create( + logRepository: mockLogRepo, + settingsRepository: mockSettingsRepository, + shouldBuffer: true, + ); final logger = Logger(_kInfoLog.logger!); logger.info(_kInfoLog.message); @@ -112,7 +123,11 @@ void main() { test('Does not buffer when off', () { TestUtils.fakeAsync((time) async { - sut = await LogService.create(logRepository: mockLogRepo, storeRepository: mockStoreRepo, shouldBuffer: false); + sut = await LogService.create( + logRepository: mockLogRepo, + settingsRepository: mockSettingsRepository, + shouldBuffer: false, + ); final logger = Logger(_kInfoLog.logger!); logger.info(_kInfoLog.message); @@ -142,7 +157,11 @@ void main() { test('Combines result from both DB + Buffer', () { TestUtils.fakeAsync((time) async { - sut = await LogService.create(logRepository: mockLogRepo, storeRepository: mockStoreRepo, shouldBuffer: true); + sut = await LogService.create( + logRepository: mockLogRepo, + settingsRepository: mockSettingsRepository, + shouldBuffer: true, + ); final logger = Logger(_kWarnLog.logger!); logger.warning(_kWarnLog.message); diff --git a/mobile/test/domain/services/store_service_test.dart b/mobile/test/domain/services/store_service_test.dart index 8ceb1e3c9c..bb439b3d72 100644 --- a/mobile/test/domain/services/store_service_test.dart +++ b/mobile/test/domain/services/store_service_test.dart @@ -9,9 +9,8 @@ import 'package:mocktail/mocktail.dart'; import '../../infrastructure/repository.mock.dart'; const _kAccessToken = '#ThisIsAToken'; -const _kBackgroundBackup = false; -const _kGroupAssetsBy = 2; -final _kBackupFailedSince = DateTime.utc(2023); +const _kAdvancedTroubleshooting = false; +const _kVersion = 2; void main() { late StoreService sut; @@ -23,16 +22,14 @@ void main() { mockDriftStoreRepo = MockDriftStoreRepository(); // For generics, we need to provide fallback to each concrete type to avoid runtime errors registerFallbackValue(StoreKey.accessToken); - registerFallbackValue(StoreKey.backupTriggerDelay); - registerFallbackValue(StoreKey.backgroundBackup); - registerFallbackValue(StoreKey.backupFailedSince); + registerFallbackValue(StoreKey.version); + registerFallbackValue(StoreKey.advancedTroubleshooting); when(() => mockDriftStoreRepo.getAll()).thenAnswer( (_) async => [ const StoreDto(StoreKey.accessToken, _kAccessToken), - const StoreDto(StoreKey.backgroundBackup, _kBackgroundBackup), - const StoreDto(StoreKey.groupAssetsBy, _kGroupAssetsBy), - StoreDto(StoreKey.backupFailedSince, _kBackupFailedSince), + const StoreDto(StoreKey.advancedTroubleshooting, _kAdvancedTroubleshooting), + const StoreDto(StoreKey.version, _kVersion), ], ); when(() => mockDriftStoreRepo.watchAll()).thenAnswer((_) => controller.stream); @@ -49,9 +46,8 @@ void main() { test('Populates the internal cache on init', () { verify(() => mockDriftStoreRepo.getAll()).called(1); expect(sut.tryGet(StoreKey.accessToken), _kAccessToken); - expect(sut.tryGet(StoreKey.backgroundBackup), _kBackgroundBackup); - expect(sut.tryGet(StoreKey.groupAssetsBy), _kGroupAssetsBy); - expect(sut.tryGet(StoreKey.backupFailedSince), _kBackupFailedSince); + expect(sut.tryGet(StoreKey.advancedTroubleshooting), _kAdvancedTroubleshooting); + expect(sut.tryGet(StoreKey.version), _kVersion); // Other keys should be null expect(sut.tryGet(StoreKey.currentUser), isNull); }); @@ -151,9 +147,8 @@ void main() { await sut.clear(); verify(() => mockDriftStoreRepo.deleteAll()).called(1); expect(sut.tryGet(StoreKey.accessToken), isNull); - expect(sut.tryGet(StoreKey.backgroundBackup), isNull); - expect(sut.tryGet(StoreKey.groupAssetsBy), isNull); - expect(sut.tryGet(StoreKey.backupFailedSince), isNull); + expect(sut.tryGet(StoreKey.advancedTroubleshooting), isNull); + expect(sut.tryGet(StoreKey.version), isNull); }); }); } diff --git a/mobile/test/domain/services/sync_stream_service_test.dart b/mobile/test/domain/services/sync_stream_service_test.dart index 1bee1dccde..e033229408 100644 --- a/mobile/test/domain/services/sync_stream_service_test.dart +++ b/mobile/test/domain/services/sync_stream_service_test.dart @@ -12,12 +12,11 @@ import 'package:immich_mobile/domain/services/sync_stream.service.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/store.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/sync_api.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/sync_stream.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/trashed_local_asset.repository.dart'; -import 'package:immich_mobile/repositories/local_files_manager.repository.dart'; +import 'package:immich_mobile/repositories/asset_media.repository.dart'; import 'package:immich_mobile/utils/semver.dart'; import 'package:mocktail/mocktail.dart'; import 'package:openapi/api.dart'; @@ -26,7 +25,6 @@ import '../../api.mocks.dart'; import '../../fixtures/asset.stub.dart'; import '../../fixtures/sync_stream.stub.dart'; import '../../infrastructure/repository.mock.dart'; -import '../../mocks/asset_entity.mock.dart'; import '../../repository.mocks.dart'; import '../../service.mocks.dart'; @@ -38,13 +36,6 @@ class _AbortCallbackWrapper { class _MockAbortCallbackWrapper extends Mock implements _AbortCallbackWrapper {} -class _CancellationWrapper { - const _CancellationWrapper(); - - bool call() => false; -} - -class _MockCancellationWrapper extends Mock implements _CancellationWrapper {} void main() { late SyncStreamService sut; @@ -52,8 +43,8 @@ void main() { late SyncApiRepository mockSyncApiRepo; late DriftLocalAssetRepository mockLocalAssetRepo; late DriftTrashedLocalAssetRepository mockTrashedLocalAssetRepo; - late LocalFilesManagerRepository mockLocalFilesManagerRepo; - late StorageRepository mockStorageRepo; + late AssetMediaRepository mockAssetMediaRepo; + late MockPermissionRepository mockPermissionRepo; late MockApiService mockApi; late MockServerApi mockServerApi; late MockSyncMigrationRepository mockSyncMigrationRepo; @@ -86,8 +77,8 @@ void main() { mockSyncApiRepo = MockSyncApiRepository(); mockLocalAssetRepo = MockLocalAssetRepository(); mockTrashedLocalAssetRepo = MockTrashedLocalAssetRepository(); - mockLocalFilesManagerRepo = MockLocalFilesManagerRepository(); - mockStorageRepo = MockStorageRepository(); + mockAssetMediaRepo = MockAssetMediaRepository(); + mockPermissionRepo = MockPermissionRepository(); mockAbortCallbackWrapper = _MockAbortCallbackWrapper(); mockResetCallbackWrapper = _MockAbortCallbackWrapper(); mockApi = MockApiService(); @@ -96,9 +87,13 @@ void main() { when(() => mockAbortCallbackWrapper()).thenReturn(false); - when(() => mockSyncApiRepo.streamChanges(any(), serverVersion: any(named: 'serverVersion'))).thenAnswer(( - invocation, - ) async { + when( + () => mockSyncApiRepo.streamChanges( + any(), + serverVersion: any(named: 'serverVersion'), + abortSignal: any(named: 'abortSignal'), + ), + ).thenAnswer((invocation) async { handleEventsCallback = invocation.positionalArguments.first; }); @@ -107,6 +102,7 @@ void main() { any(), onReset: any(named: 'onReset'), serverVersion: any(named: 'serverVersion'), + abortSignal: any(named: 'abortSignal'), ), ).thenAnswer((invocation) async { handleEventsCallback = invocation.positionalArguments.first; @@ -118,7 +114,7 @@ void main() { when(() => mockApi.serverInfoApi).thenReturn(mockServerApi); when( () => mockServerApi.getServerVersion(), - ).thenAnswer((_) async => ServerVersionResponseDto(major: 1, minor: 132, patch_: 0)); + ).thenAnswer((_) async => ServerVersionResponseDto(major: 1, minor: 132, patch_: 0, prerelease: null)); when(() => mockSyncStreamRepo.updateUsersV1(any())).thenAnswer(successHandler); when(() => mockSyncStreamRepo.deleteUsersV1(any())).thenAnswer(successHandler); @@ -159,8 +155,8 @@ void main() { syncStreamRepository: mockSyncStreamRepo, localAssetRepository: mockLocalAssetRepo, trashedLocalAssetRepository: mockTrashedLocalAssetRepo, - localFilesManager: mockLocalFilesManagerRepo, - storageRepository: mockStorageRepo, + assetMediaRepository: mockAssetMediaRepo, + permissionRepository: mockPermissionRepo, api: mockApi, syncMigrationRepository: mockSyncMigrationRepo, ); @@ -170,10 +166,12 @@ void main() { when(() => mockTrashedLocalAssetRepo.getToRestore()).thenAnswer((_) async => []); when(() => mockTrashedLocalAssetRepo.applyRestoredAssets(any())).thenAnswer((_) async {}); hasManageMediaPermission = false; - when(() => mockLocalFilesManagerRepo.hasManageMediaPermission()).thenAnswer((_) async => hasManageMediaPermission); - when(() => mockLocalFilesManagerRepo.moveToTrash(any())).thenAnswer((_) async => true); - when(() => mockLocalFilesManagerRepo.restoreAssetsFromTrash(any())).thenAnswer((_) async => []); - when(() => mockStorageRepo.getAssetEntityForAsset(any())).thenAnswer((_) async => null); + when(() => mockPermissionRepo.hasManageMediaPermission()).thenAnswer((_) async => hasManageMediaPermission); + when(() => mockAssetMediaRepo.deleteAll(any())).thenAnswer((invocation) async { + final ids = invocation.positionalArguments.first as List; + return ids; + }); + when(() => mockAssetMediaRepo.restoreAssetsFromTrash(any())).thenAnswer((_) async => []); await Store.put(StoreKey.manageLocalMediaAndroid, false); }); @@ -233,17 +231,16 @@ void main() { }); test("aborts and stops processing if cancelled during iteration", () async { - final cancellationChecker = _MockCancellationWrapper(); - when(() => cancellationChecker()).thenReturn(false); + final cancellation = Completer(); sut = SyncStreamService( syncApiRepository: mockSyncApiRepo, syncStreamRepository: mockSyncStreamRepo, localAssetRepository: mockLocalAssetRepo, trashedLocalAssetRepository: mockTrashedLocalAssetRepo, - localFilesManager: mockLocalFilesManagerRepo, - storageRepository: mockStorageRepo, - cancelChecker: cancellationChecker.call, + assetMediaRepository: mockAssetMediaRepo, + permissionRepository: mockPermissionRepo, + cancellation: cancellation, api: mockApi, syncMigrationRepository: mockSyncMigrationRepo, ); @@ -252,7 +249,7 @@ void main() { final events = [SyncStreamStub.userDeleteV1, SyncStreamStub.userV1Admin, SyncStreamStub.partnerDeleteV1]; when(() => mockSyncStreamRepo.deleteUsersV1(any())).thenAnswer((_) async { - when(() => cancellationChecker()).thenReturn(true); + cancellation.complete(); }); await handleEventsCallback(events, mockAbortCallbackWrapper.call, mockResetCallbackWrapper.call); @@ -267,8 +264,7 @@ void main() { }); test("aborts and stops processing if cancelled before processing batch", () async { - final cancellationChecker = _MockCancellationWrapper(); - when(() => cancellationChecker()).thenReturn(false); + final cancellation = Completer(); final processingCompleter = Completer(); bool handler1Started = false; @@ -282,9 +278,9 @@ void main() { syncStreamRepository: mockSyncStreamRepo, localAssetRepository: mockLocalAssetRepo, trashedLocalAssetRepository: mockTrashedLocalAssetRepo, - localFilesManager: mockLocalFilesManagerRepo, - storageRepository: mockStorageRepo, - cancelChecker: cancellationChecker.call, + assetMediaRepository: mockAssetMediaRepo, + permissionRepository: mockPermissionRepo, + cancellation: cancellation, api: mockApi, syncMigrationRepository: mockSyncMigrationRepo, ); @@ -303,7 +299,7 @@ void main() { expect(handler1Started, isTrue); // Signal cancellation while handler 1 is waiting - when(() => cancellationChecker()).thenReturn(true); + cancellation.complete(); await pumpEventQueue(); processingCompleter.complete(); @@ -424,18 +420,10 @@ void main() { return assetsByAlbum; }); - final localEntity = MockAssetEntity(); - when(() => localEntity.getMediaUrl()).thenAnswer((_) async => 'content://local-only'); - when(() => mockStorageRepo.getAssetEntityForAsset(localAsset)).thenAnswer((_) async => localEntity); - - final mergedEntity = MockAssetEntity(); - when(() => mergedEntity.getMediaUrl()).thenAnswer((_) async => 'content://merged-local'); - when(() => mockStorageRepo.getAssetEntityForAsset(mergedAsset)).thenAnswer((_) async => mergedEntity); - - when(() => mockLocalFilesManagerRepo.moveToTrash(any())).thenAnswer((invocation) async { - final urls = invocation.positionalArguments.first as List; - expect(urls, unorderedEquals(['content://local-only', 'content://merged-local'])); - return true; + when(() => mockAssetMediaRepo.deleteAll(any())).thenAnswer((invocation) async { + final ids = invocation.positionalArguments.first as List; + expect(ids, unorderedEquals(['local-only', 'merged-local'])); + return ids; }); final events = [ @@ -461,10 +449,51 @@ void main() { await simulateEvents(events); - verify(() => mockTrashedLocalAssetRepo.trashLocalAsset(assetsByAlbum)).called(1); + final trashArgs = + verify(() => mockTrashedLocalAssetRepo.trashLocalAsset(captureAny())).captured.single + as Map>; + expect(trashArgs.keys, unorderedEquals(['album-a', 'album-b'])); + expect(trashArgs['album-a'], [localAsset]); + expect(trashArgs['album-b'], [mergedAsset]); + verify(() => mockAssetMediaRepo.deleteAll(any())).called(1); verify(() => mockSyncApiRepo.ack(['asset-remote-only-3'])).called(1); }); + test("records only assets that were moved to device trash", () async { + final movedAsset = LocalAssetStub.image1.copyWith(id: 'moved-local', checksum: 'checksum-moved'); + final skippedAsset = LocalAssetStub.image2.copyWith(id: 'skipped-local', checksum: 'checksum-skipped'); + when(() => mockLocalAssetRepo.getAssetsFromBackupAlbums(any())).thenAnswer( + (_) async => { + 'album-a': [movedAsset], + 'album-b': [skippedAsset], + }, + ); + when(() => mockAssetMediaRepo.deleteAll(any())).thenAnswer((_) async => ['moved-local']); + + final events = [ + SyncStreamStub.assetTrashed( + id: 'remote-moved', + checksum: movedAsset.checksum!, + ack: 'asset-remote-moved', + trashedAt: DateTime(2025, 5, 1), + ), + SyncStreamStub.assetTrashed( + id: 'remote-skipped', + checksum: skippedAsset.checksum!, + ack: 'asset-remote-skipped', + trashedAt: DateTime(2025, 5, 2), + ), + ]; + + await simulateEvents(events); + + final trashArgs = + verify(() => mockTrashedLocalAssetRepo.trashLocalAsset(captureAny())).captured.single + as Map>; + expect(trashArgs.keys, ['album-a']); + expect(trashArgs['album-a'], [movedAsset]); + }); + test("skips device trashing when no local assets match the remote trash payload", () async { final events = [ SyncStreamStub.assetTrashed( @@ -478,7 +507,7 @@ void main() { await simulateEvents(events); verify(() => mockLocalAssetRepo.getAssetsFromBackupAlbums(any())).called(1); - verifyNever(() => mockLocalFilesManagerRepo.moveToTrash(any())); + verifyNever(() => mockAssetMediaRepo.deleteAll(any())); verifyNever(() => mockTrashedLocalAssetRepo.trashLocalAsset(any())); }); @@ -494,7 +523,7 @@ void main() { await simulateEvents(events); verify(() => mockLocalAssetRepo.getAssetsFromBackupAlbums(any())).called(1); - verifyNever(() => mockLocalFilesManagerRepo.moveToTrash(any())); + verifyNever(() => mockAssetMediaRepo.deleteAll(any())); verify(() => mockSyncStreamRepo.deleteAssetsV1(any())).called(1); }); @@ -505,7 +534,7 @@ void main() { when(() => mockTrashedLocalAssetRepo.getToRestore()).thenAnswer((_) async => trashedAssets); final restoredIds = ['trashed-1']; - when(() => mockLocalFilesManagerRepo.restoreAssetsFromTrash(any())).thenAnswer((invocation) async { + when(() => mockAssetMediaRepo.restoreAssetsFromTrash(any())).thenAnswer((invocation) async { final Iterable requestedAssets = invocation.positionalArguments.first as Iterable; expect(requestedAssets, orderedEquals(trashedAssets)); return restoredIds; @@ -526,7 +555,7 @@ void main() { await Store.put(StoreKey.syncMigrationStatus, "[]"); when( () => mockServerApi.getServerVersion(), - ).thenAnswer((_) async => ServerVersionResponseDto(major: 2, minor: 4, patch_: 1)); + ).thenAnswer((_) async => ServerVersionResponseDto(major: 2, minor: 4, patch_: 1, prerelease: null)); await sut.sync(); @@ -554,7 +583,7 @@ void main() { await Store.put(StoreKey.syncMigrationStatus, "[]"); when( () => mockServerApi.getServerVersion(), - ).thenAnswer((_) async => ServerVersionResponseDto(major: 2, minor: 5, patch_: 0)); + ).thenAnswer((_) async => ServerVersionResponseDto(major: 2, minor: 5, patch_: 0, prerelease: null)); await sut.sync(); verifyInOrder([ @@ -584,7 +613,7 @@ void main() { when( () => mockServerApi.getServerVersion(), - ).thenAnswer((_) async => ServerVersionResponseDto(major: 2, minor: 4, patch_: 1)); + ).thenAnswer((_) async => ServerVersionResponseDto(major: 2, minor: 4, patch_: 1, prerelease: null)); await sut.sync(); diff --git a/mobile/test/drift/main/generated/schema.dart b/mobile/test/drift/main/generated/schema.dart index 3014477c55..f03fa4a593 100644 --- a/mobile/test/drift/main/generated/schema.dart +++ b/mobile/test/drift/main/generated/schema.dart @@ -29,6 +29,10 @@ import 'schema_v22.dart' as v22; import 'schema_v23.dart' as v23; import 'schema_v24.dart' as v24; import 'schema_v25.dart' as v25; +import 'schema_v26.dart' as v26; +import 'schema_v27.dart' as v27; +import 'schema_v28.dart' as v28; +import 'schema_v29.dart' as v29; class GeneratedHelper implements SchemaInstantiationHelper { @override @@ -84,6 +88,14 @@ class GeneratedHelper implements SchemaInstantiationHelper { return v24.DatabaseAtV24(db); case 25: return v25.DatabaseAtV25(db); + case 26: + return v26.DatabaseAtV26(db); + case 27: + return v27.DatabaseAtV27(db); + case 28: + return v28.DatabaseAtV28(db); + case 29: + return v29.DatabaseAtV29(db); default: throw MissingSchemaException(version, versions); } @@ -115,5 +127,9 @@ class GeneratedHelper implements SchemaInstantiationHelper { 23, 24, 25, + 26, + 27, + 28, + 29, ]; } diff --git a/mobile/test/drift/main/generated/schema_v25.dart b/mobile/test/drift/main/generated/schema_v25.dart index 54f092d2d5..aad45f0bd3 100644 --- a/mobile/test/drift/main/generated/schema_v25.dart +++ b/mobile/test/drift/main/generated/schema_v25.dart @@ -8792,216 +8792,67 @@ class AssetEditEntityCompanion extends UpdateCompanion { } } -class AssetOcrEntity extends Table - with TableInfo { +class Metadata extends Table with TableInfo { @override final GeneratedDatabase attachedDatabase; final String? _alias; - AssetOcrEntity(this.attachedDatabase, [this._alias]); - late final GeneratedColumn id = GeneratedColumn( - 'id', + Metadata(this.attachedDatabase, [this._alias]); + late final GeneratedColumn key = GeneratedColumn( + 'key', aliasedName, false, type: DriftSqlType.string, requiredDuringInsert: true, $customConstraints: 'NOT NULL', ); - late final GeneratedColumn assetId = GeneratedColumn( - 'asset_id', - aliasedName, - false, - type: DriftSqlType.string, - requiredDuringInsert: true, - $customConstraints: - 'NOT NULL REFERENCES remote_asset_entity(id)ON DELETE CASCADE', - ); - late final GeneratedColumn x1 = GeneratedColumn( - 'x1', - aliasedName, - false, - type: DriftSqlType.double, - requiredDuringInsert: true, - $customConstraints: 'NOT NULL', - ); - late final GeneratedColumn y1 = GeneratedColumn( - 'y1', - aliasedName, - false, - type: DriftSqlType.double, - requiredDuringInsert: true, - $customConstraints: 'NOT NULL', - ); - late final GeneratedColumn x2 = GeneratedColumn( - 'x2', - aliasedName, - false, - type: DriftSqlType.double, - requiredDuringInsert: true, - $customConstraints: 'NOT NULL', - ); - late final GeneratedColumn y2 = GeneratedColumn( - 'y2', - aliasedName, - false, - type: DriftSqlType.double, - requiredDuringInsert: true, - $customConstraints: 'NOT NULL', - ); - late final GeneratedColumn x3 = GeneratedColumn( - 'x3', - aliasedName, - false, - type: DriftSqlType.double, - requiredDuringInsert: true, - $customConstraints: 'NOT NULL', - ); - late final GeneratedColumn y3 = GeneratedColumn( - 'y3', - aliasedName, - false, - type: DriftSqlType.double, - requiredDuringInsert: true, - $customConstraints: 'NOT NULL', - ); - late final GeneratedColumn x4 = GeneratedColumn( - 'x4', - aliasedName, - false, - type: DriftSqlType.double, - requiredDuringInsert: true, - $customConstraints: 'NOT NULL', - ); - late final GeneratedColumn y4 = GeneratedColumn( - 'y4', - aliasedName, - false, - type: DriftSqlType.double, - requiredDuringInsert: true, - $customConstraints: 'NOT NULL', - ); - late final GeneratedColumn boxScore = GeneratedColumn( - 'box_score', - aliasedName, - false, - type: DriftSqlType.double, - requiredDuringInsert: true, - $customConstraints: 'NOT NULL', - ); - late final GeneratedColumn textScore = GeneratedColumn( - 'text_score', - aliasedName, - false, - type: DriftSqlType.double, - requiredDuringInsert: true, - $customConstraints: 'NOT NULL', - ); - late final GeneratedColumn recognizedText = GeneratedColumn( - 'recognized_text', + late final GeneratedColumn value = GeneratedColumn( + 'value', aliasedName, false, type: DriftSqlType.string, requiredDuringInsert: true, $customConstraints: 'NOT NULL', ); - late final GeneratedColumn isVisible = GeneratedColumn( - 'is_visible', + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', aliasedName, false, - type: DriftSqlType.int, + type: DriftSqlType.string, requiredDuringInsert: false, - $customConstraints: 'NOT NULL DEFAULT 1 CHECK (is_visible IN (0, 1))', - defaultValue: const CustomExpression('1'), + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), ); @override - List get $columns => [ - id, - assetId, - x1, - y1, - x2, - y2, - x3, - y3, - x4, - y4, - boxScore, - textScore, - recognizedText, - isVisible, - ]; + List get $columns => [key, value, updatedAt]; @override String get aliasedName => _alias ?? actualTableName; @override String get actualTableName => $name; - static const String $name = 'asset_ocr_entity'; + static const String $name = 'metadata'; @override - Set get $primaryKey => {id}; + Set get $primaryKey => {key}; @override - AssetOcrEntityData map(Map data, {String? tablePrefix}) { + MetadataData map(Map data, {String? tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; - return AssetOcrEntityData( - id: attachedDatabase.typeMapping.read( + return MetadataData( + key: attachedDatabase.typeMapping.read( DriftSqlType.string, - data['${effectivePrefix}id'], + data['${effectivePrefix}key'], )!, - assetId: attachedDatabase.typeMapping.read( + value: attachedDatabase.typeMapping.read( DriftSqlType.string, - data['${effectivePrefix}asset_id'], + data['${effectivePrefix}value'], )!, - x1: attachedDatabase.typeMapping.read( - DriftSqlType.double, - data['${effectivePrefix}x1'], - )!, - y1: attachedDatabase.typeMapping.read( - DriftSqlType.double, - data['${effectivePrefix}y1'], - )!, - x2: attachedDatabase.typeMapping.read( - DriftSqlType.double, - data['${effectivePrefix}x2'], - )!, - y2: attachedDatabase.typeMapping.read( - DriftSqlType.double, - data['${effectivePrefix}y2'], - )!, - x3: attachedDatabase.typeMapping.read( - DriftSqlType.double, - data['${effectivePrefix}x3'], - )!, - y3: attachedDatabase.typeMapping.read( - DriftSqlType.double, - data['${effectivePrefix}y3'], - )!, - x4: attachedDatabase.typeMapping.read( - DriftSqlType.double, - data['${effectivePrefix}x4'], - )!, - y4: attachedDatabase.typeMapping.read( - DriftSqlType.double, - data['${effectivePrefix}y4'], - )!, - boxScore: attachedDatabase.typeMapping.read( - DriftSqlType.double, - data['${effectivePrefix}box_score'], - )!, - textScore: attachedDatabase.typeMapping.read( - DriftSqlType.double, - data['${effectivePrefix}text_score'], - )!, - recognizedText: attachedDatabase.typeMapping.read( + updatedAt: attachedDatabase.typeMapping.read( DriftSqlType.string, - data['${effectivePrefix}recognized_text'], - )!, - isVisible: attachedDatabase.typeMapping.read( - DriftSqlType.int, - data['${effectivePrefix}is_visible'], + data['${effectivePrefix}updated_at'], )!, ); } @override - AssetOcrEntity createAlias(String alias) { - return AssetOcrEntity(attachedDatabase, alias); + Metadata createAlias(String alias) { + return Metadata(attachedDatabase, alias); } @override @@ -9009,408 +8860,145 @@ class AssetOcrEntity extends Table @override bool get isStrict => true; @override - List get customConstraints => const ['PRIMARY KEY(id)']; + List get customConstraints => const ['PRIMARY KEY("key")']; @override bool get dontWriteConstraints => true; } -class AssetOcrEntityData extends DataClass - implements Insertable { - final String id; - final String assetId; - final double x1; - final double y1; - final double x2; - final double y2; - final double x3; - final double y3; - final double x4; - final double y4; - final double boxScore; - final double textScore; - final String recognizedText; - final int isVisible; - const AssetOcrEntityData({ - required this.id, - required this.assetId, - required this.x1, - required this.y1, - required this.x2, - required this.y2, - required this.x3, - required this.y3, - required this.x4, - required this.y4, - required this.boxScore, - required this.textScore, - required this.recognizedText, - required this.isVisible, +class MetadataData extends DataClass implements Insertable { + final String key; + final String value; + final String updatedAt; + const MetadataData({ + required this.key, + required this.value, + required this.updatedAt, }); @override Map toColumns(bool nullToAbsent) { final map = {}; - map['id'] = Variable(id); - map['asset_id'] = Variable(assetId); - map['x1'] = Variable(x1); - map['y1'] = Variable(y1); - map['x2'] = Variable(x2); - map['y2'] = Variable(y2); - map['x3'] = Variable(x3); - map['y3'] = Variable(y3); - map['x4'] = Variable(x4); - map['y4'] = Variable(y4); - map['box_score'] = Variable(boxScore); - map['text_score'] = Variable(textScore); - map['recognized_text'] = Variable(recognizedText); - map['is_visible'] = Variable(isVisible); + map['key'] = Variable(key); + map['value'] = Variable(value); + map['updated_at'] = Variable(updatedAt); return map; } - factory AssetOcrEntityData.fromJson( + factory MetadataData.fromJson( Map json, { ValueSerializer? serializer, }) { serializer ??= driftRuntimeOptions.defaultSerializer; - return AssetOcrEntityData( - id: serializer.fromJson(json['id']), - assetId: serializer.fromJson(json['assetId']), - x1: serializer.fromJson(json['x1']), - y1: serializer.fromJson(json['y1']), - x2: serializer.fromJson(json['x2']), - y2: serializer.fromJson(json['y2']), - x3: serializer.fromJson(json['x3']), - y3: serializer.fromJson(json['y3']), - x4: serializer.fromJson(json['x4']), - y4: serializer.fromJson(json['y4']), - boxScore: serializer.fromJson(json['boxScore']), - textScore: serializer.fromJson(json['textScore']), - recognizedText: serializer.fromJson(json['recognizedText']), - isVisible: serializer.fromJson(json['isVisible']), + return MetadataData( + key: serializer.fromJson(json['key']), + value: serializer.fromJson(json['value']), + updatedAt: serializer.fromJson(json['updatedAt']), ); } @override Map toJson({ValueSerializer? serializer}) { serializer ??= driftRuntimeOptions.defaultSerializer; return { - 'id': serializer.toJson(id), - 'assetId': serializer.toJson(assetId), - 'x1': serializer.toJson(x1), - 'y1': serializer.toJson(y1), - 'x2': serializer.toJson(x2), - 'y2': serializer.toJson(y2), - 'x3': serializer.toJson(x3), - 'y3': serializer.toJson(y3), - 'x4': serializer.toJson(x4), - 'y4': serializer.toJson(y4), - 'boxScore': serializer.toJson(boxScore), - 'textScore': serializer.toJson(textScore), - 'recognizedText': serializer.toJson(recognizedText), - 'isVisible': serializer.toJson(isVisible), + 'key': serializer.toJson(key), + 'value': serializer.toJson(value), + 'updatedAt': serializer.toJson(updatedAt), }; } - AssetOcrEntityData copyWith({ - String? id, - String? assetId, - double? x1, - double? y1, - double? x2, - double? y2, - double? x3, - double? y3, - double? x4, - double? y4, - double? boxScore, - double? textScore, - String? recognizedText, - int? isVisible, - }) => AssetOcrEntityData( - id: id ?? this.id, - assetId: assetId ?? this.assetId, - x1: x1 ?? this.x1, - y1: y1 ?? this.y1, - x2: x2 ?? this.x2, - y2: y2 ?? this.y2, - x3: x3 ?? this.x3, - y3: y3 ?? this.y3, - x4: x4 ?? this.x4, - y4: y4 ?? this.y4, - boxScore: boxScore ?? this.boxScore, - textScore: textScore ?? this.textScore, - recognizedText: recognizedText ?? this.recognizedText, - isVisible: isVisible ?? this.isVisible, - ); - AssetOcrEntityData copyWithCompanion(AssetOcrEntityCompanion data) { - return AssetOcrEntityData( - id: data.id.present ? data.id.value : this.id, - assetId: data.assetId.present ? data.assetId.value : this.assetId, - x1: data.x1.present ? data.x1.value : this.x1, - y1: data.y1.present ? data.y1.value : this.y1, - x2: data.x2.present ? data.x2.value : this.x2, - y2: data.y2.present ? data.y2.value : this.y2, - x3: data.x3.present ? data.x3.value : this.x3, - y3: data.y3.present ? data.y3.value : this.y3, - x4: data.x4.present ? data.x4.value : this.x4, - y4: data.y4.present ? data.y4.value : this.y4, - boxScore: data.boxScore.present ? data.boxScore.value : this.boxScore, - textScore: data.textScore.present ? data.textScore.value : this.textScore, - recognizedText: data.recognizedText.present - ? data.recognizedText.value - : this.recognizedText, - isVisible: data.isVisible.present ? data.isVisible.value : this.isVisible, + MetadataData copyWith({String? key, String? value, String? updatedAt}) => + MetadataData( + key: key ?? this.key, + value: value ?? this.value, + updatedAt: updatedAt ?? this.updatedAt, + ); + MetadataData copyWithCompanion(MetadataCompanion data) { + return MetadataData( + key: data.key.present ? data.key.value : this.key, + value: data.value.present ? data.value.value : this.value, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, ); } @override String toString() { - return (StringBuffer('AssetOcrEntityData(') - ..write('id: $id, ') - ..write('assetId: $assetId, ') - ..write('x1: $x1, ') - ..write('y1: $y1, ') - ..write('x2: $x2, ') - ..write('y2: $y2, ') - ..write('x3: $x3, ') - ..write('y3: $y3, ') - ..write('x4: $x4, ') - ..write('y4: $y4, ') - ..write('boxScore: $boxScore, ') - ..write('textScore: $textScore, ') - ..write('recognizedText: $recognizedText, ') - ..write('isVisible: $isVisible') + return (StringBuffer('MetadataData(') + ..write('key: $key, ') + ..write('value: $value, ') + ..write('updatedAt: $updatedAt') ..write(')')) .toString(); } @override - int get hashCode => Object.hash( - id, - assetId, - x1, - y1, - x2, - y2, - x3, - y3, - x4, - y4, - boxScore, - textScore, - recognizedText, - isVisible, - ); + int get hashCode => Object.hash(key, value, updatedAt); @override bool operator ==(Object other) => identical(this, other) || - (other is AssetOcrEntityData && - other.id == this.id && - other.assetId == this.assetId && - other.x1 == this.x1 && - other.y1 == this.y1 && - other.x2 == this.x2 && - other.y2 == this.y2 && - other.x3 == this.x3 && - other.y3 == this.y3 && - other.x4 == this.x4 && - other.y4 == this.y4 && - other.boxScore == this.boxScore && - other.textScore == this.textScore && - other.recognizedText == this.recognizedText && - other.isVisible == this.isVisible); + (other is MetadataData && + other.key == this.key && + other.value == this.value && + other.updatedAt == this.updatedAt); } -class AssetOcrEntityCompanion extends UpdateCompanion { - final Value id; - final Value assetId; - final Value x1; - final Value y1; - final Value x2; - final Value y2; - final Value x3; - final Value y3; - final Value x4; - final Value y4; - final Value boxScore; - final Value textScore; - final Value recognizedText; - final Value isVisible; - const AssetOcrEntityCompanion({ - this.id = const Value.absent(), - this.assetId = const Value.absent(), - this.x1 = const Value.absent(), - this.y1 = const Value.absent(), - this.x2 = const Value.absent(), - this.y2 = const Value.absent(), - this.x3 = const Value.absent(), - this.y3 = const Value.absent(), - this.x4 = const Value.absent(), - this.y4 = const Value.absent(), - this.boxScore = const Value.absent(), - this.textScore = const Value.absent(), - this.recognizedText = const Value.absent(), - this.isVisible = const Value.absent(), +class MetadataCompanion extends UpdateCompanion { + final Value key; + final Value value; + final Value updatedAt; + const MetadataCompanion({ + this.key = const Value.absent(), + this.value = const Value.absent(), + this.updatedAt = const Value.absent(), }); - AssetOcrEntityCompanion.insert({ - required String id, - required String assetId, - required double x1, - required double y1, - required double x2, - required double y2, - required double x3, - required double y3, - required double x4, - required double y4, - required double boxScore, - required double textScore, - required String recognizedText, - this.isVisible = const Value.absent(), - }) : id = Value(id), - assetId = Value(assetId), - x1 = Value(x1), - y1 = Value(y1), - x2 = Value(x2), - y2 = Value(y2), - x3 = Value(x3), - y3 = Value(y3), - x4 = Value(x4), - y4 = Value(y4), - boxScore = Value(boxScore), - textScore = Value(textScore), - recognizedText = Value(recognizedText); - static Insertable custom({ - Expression? id, - Expression? assetId, - Expression? x1, - Expression? y1, - Expression? x2, - Expression? y2, - Expression? x3, - Expression? y3, - Expression? x4, - Expression? y4, - Expression? boxScore, - Expression? textScore, - Expression? recognizedText, - Expression? isVisible, + MetadataCompanion.insert({ + required String key, + required String value, + this.updatedAt = const Value.absent(), + }) : key = Value(key), + value = Value(value); + static Insertable custom({ + Expression? key, + Expression? value, + Expression? updatedAt, }) { return RawValuesInsertable({ - if (id != null) 'id': id, - if (assetId != null) 'asset_id': assetId, - if (x1 != null) 'x1': x1, - if (y1 != null) 'y1': y1, - if (x2 != null) 'x2': x2, - if (y2 != null) 'y2': y2, - if (x3 != null) 'x3': x3, - if (y3 != null) 'y3': y3, - if (x4 != null) 'x4': x4, - if (y4 != null) 'y4': y4, - if (boxScore != null) 'box_score': boxScore, - if (textScore != null) 'text_score': textScore, - if (recognizedText != null) 'recognized_text': recognizedText, - if (isVisible != null) 'is_visible': isVisible, + if (key != null) 'key': key, + if (value != null) 'value': value, + if (updatedAt != null) 'updated_at': updatedAt, }); } - AssetOcrEntityCompanion copyWith({ - Value? id, - Value? assetId, - Value? x1, - Value? y1, - Value? x2, - Value? y2, - Value? x3, - Value? y3, - Value? x4, - Value? y4, - Value? boxScore, - Value? textScore, - Value? recognizedText, - Value? isVisible, + MetadataCompanion copyWith({ + Value? key, + Value? value, + Value? updatedAt, }) { - return AssetOcrEntityCompanion( - id: id ?? this.id, - assetId: assetId ?? this.assetId, - x1: x1 ?? this.x1, - y1: y1 ?? this.y1, - x2: x2 ?? this.x2, - y2: y2 ?? this.y2, - x3: x3 ?? this.x3, - y3: y3 ?? this.y3, - x4: x4 ?? this.x4, - y4: y4 ?? this.y4, - boxScore: boxScore ?? this.boxScore, - textScore: textScore ?? this.textScore, - recognizedText: recognizedText ?? this.recognizedText, - isVisible: isVisible ?? this.isVisible, + return MetadataCompanion( + key: key ?? this.key, + value: value ?? this.value, + updatedAt: updatedAt ?? this.updatedAt, ); } @override Map toColumns(bool nullToAbsent) { final map = {}; - if (id.present) { - map['id'] = Variable(id.value); + if (key.present) { + map['key'] = Variable(key.value); } - if (assetId.present) { - map['asset_id'] = Variable(assetId.value); + if (value.present) { + map['value'] = Variable(value.value); } - if (x1.present) { - map['x1'] = Variable(x1.value); - } - if (y1.present) { - map['y1'] = Variable(y1.value); - } - if (x2.present) { - map['x2'] = Variable(x2.value); - } - if (y2.present) { - map['y2'] = Variable(y2.value); - } - if (x3.present) { - map['x3'] = Variable(x3.value); - } - if (y3.present) { - map['y3'] = Variable(y3.value); - } - if (x4.present) { - map['x4'] = Variable(x4.value); - } - if (y4.present) { - map['y4'] = Variable(y4.value); - } - if (boxScore.present) { - map['box_score'] = Variable(boxScore.value); - } - if (textScore.present) { - map['text_score'] = Variable(textScore.value); - } - if (recognizedText.present) { - map['recognized_text'] = Variable(recognizedText.value); - } - if (isVisible.present) { - map['is_visible'] = Variable(isVisible.value); + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); } return map; } @override String toString() { - return (StringBuffer('AssetOcrEntityCompanion(') - ..write('id: $id, ') - ..write('assetId: $assetId, ') - ..write('x1: $x1, ') - ..write('y1: $y1, ') - ..write('x2: $x2, ') - ..write('y2: $y2, ') - ..write('x3: $x3, ') - ..write('y3: $y3, ') - ..write('x4: $x4, ') - ..write('y4: $y4, ') - ..write('boxScore: $boxScore, ') - ..write('textScore: $textScore, ') - ..write('recognizedText: $recognizedText, ') - ..write('isVisible: $isVisible') + return (StringBuffer('MetadataCompanion(') + ..write('key: $key, ') + ..write('value: $value, ') + ..write('updatedAt: $updatedAt') ..write(')')) .toString(); } @@ -9442,10 +9030,6 @@ class DatabaseAtV25 extends GeneratedDatabase { 'idx_stack_primary_asset_id', 'CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)', ); - late final Index idxRemoteAssetOwnerChecksum = Index( - 'idx_remote_asset_owner_checksum', - 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)', - ); late final Index uQRemoteAssetsOwnerChecksum = Index( 'UQ_remote_assets_owner_checksum', 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)', @@ -9462,13 +9046,9 @@ class DatabaseAtV25 extends GeneratedDatabase { 'idx_remote_asset_stack_id', 'CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)', ); - late final Index idxRemoteAssetLocalDateTimeDay = Index( - 'idx_remote_asset_local_date_time_day', - 'CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_day ON remote_asset_entity (STRFTIME(\'%Y-%m-%d\', local_date_time))', - ); - late final Index idxRemoteAssetLocalDateTimeMonth = Index( - 'idx_remote_asset_local_date_time_month', - 'CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_month ON remote_asset_entity (STRFTIME(\'%Y-%m\', local_date_time))', + late final Index idxRemoteAssetOwnerVisibilityDeletedCreated = Index( + 'idx_remote_asset_owner_visibility_deleted_created', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_visibility_deleted_created ON remote_asset_entity (owner_id, visibility, deleted_at, created_at DESC)', ); late final AuthUserEntity authUserEntity = AuthUserEntity(this); late final UserMetadataEntity userMetadataEntity = UserMetadataEntity(this); @@ -9488,7 +9068,7 @@ class DatabaseAtV25 extends GeneratedDatabase { late final TrashedLocalAssetEntity trashedLocalAssetEntity = TrashedLocalAssetEntity(this); late final AssetEditEntity assetEditEntity = AssetEditEntity(this); - late final AssetOcrEntity assetOcrEntity = AssetOcrEntity(this); + late final Metadata metadata = Metadata(this); late final Index idxPartnerSharedWithId = Index( 'idx_partner_shared_with_id', 'CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)', @@ -9497,6 +9077,10 @@ class DatabaseAtV25 extends GeneratedDatabase { 'idx_lat_lng', 'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)', ); + late final Index idxRemoteExifCity = Index( + 'idx_remote_exif_city', + 'CREATE INDEX IF NOT EXISTS idx_remote_exif_city ON remote_exif_entity (city) WHERE city IS NOT NULL', + ); late final Index idxRemoteAlbumAssetAlbumAsset = Index( 'idx_remote_album_asset_album_asset', 'CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)', @@ -9517,6 +9101,10 @@ class DatabaseAtV25 extends GeneratedDatabase { 'idx_asset_face_asset_id', 'CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)', ); + late final Index idxAssetFaceVisiblePerson = Index( + 'idx_asset_face_visible_person', + 'CREATE INDEX IF NOT EXISTS idx_asset_face_visible_person ON asset_face_entity (person_id, asset_id) WHERE is_visible = 1 AND deleted_at IS NULL', + ); late final Index idxTrashedLocalAssetChecksum = Index( 'idx_trashed_local_asset_checksum', 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)', @@ -9545,13 +9133,11 @@ class DatabaseAtV25 extends GeneratedDatabase { idxLocalAssetChecksum, idxLocalAssetCloudId, idxStackPrimaryAssetId, - idxRemoteAssetOwnerChecksum, uQRemoteAssetsOwnerChecksum, uQRemoteAssetsOwnerLibraryChecksum, idxRemoteAssetChecksum, idxRemoteAssetStackId, - idxRemoteAssetLocalDateTimeDay, - idxRemoteAssetLocalDateTimeMonth, + idxRemoteAssetOwnerVisibilityDeletedCreated, authUserEntity, userMetadataEntity, partnerEntity, @@ -9566,14 +9152,16 @@ class DatabaseAtV25 extends GeneratedDatabase { storeEntity, trashedLocalAssetEntity, assetEditEntity, - assetOcrEntity, + metadata, idxPartnerSharedWithId, idxLatLng, + idxRemoteExifCity, idxRemoteAlbumAssetAlbumAsset, idxRemoteAssetCloudId, idxPersonOwnerId, idxAssetFacePersonId, idxAssetFaceAssetId, + idxAssetFaceVisiblePerson, idxTrashedLocalAssetChecksum, idxTrashedLocalAssetAlbum, idxAssetEditAssetId, @@ -9748,13 +9336,6 @@ class DatabaseAtV25 extends GeneratedDatabase { ), result: [TableUpdate('asset_edit_entity', kind: UpdateKind.delete)], ), - WritePropagation( - on: TableUpdateQuery.onTableName( - 'remote_asset_entity', - limitUpdateKind: UpdateKind.delete, - ), - result: [TableUpdate('asset_ocr_entity', kind: UpdateKind.delete)], - ), ]); @override int get schemaVersion => 25; diff --git a/mobile/test/drift/main/generated/schema_v26.dart b/mobile/test/drift/main/generated/schema_v26.dart new file mode 100644 index 0000000000..b91afd1b8a --- /dev/null +++ b/mobile/test/drift/main/generated/schema_v26.dart @@ -0,0 +1,9384 @@ +// dart format width=80 +import 'dart:typed_data' as i2; +// GENERATED BY drift_dev, DO NOT MODIFY. +// ignore_for_file: type=lint,unused_import +// +import 'package:drift/drift.dart'; + +class UserEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn email = GeneratedColumn( + 'email', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn hasProfileImage = GeneratedColumn( + 'has_profile_image', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: + 'NOT NULL DEFAULT 0 CHECK (has_profile_image IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn profileChangedAt = GeneratedColumn( + 'profile_changed_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn avatarColor = GeneratedColumn( + 'avatar_color', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0', + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + id, + name, + email, + hasProfileImage, + profileChangedAt, + avatarColor, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_entity'; + @override + Set get $primaryKey => {id}; + @override + UserEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + email: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}email'], + )!, + hasProfileImage: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}has_profile_image'], + )!, + profileChangedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}profile_changed_at'], + )!, + avatarColor: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}avatar_color'], + )!, + ); + } + + @override + UserEntity createAlias(String alias) { + return UserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class UserEntityData extends DataClass implements Insertable { + final String id; + final String name; + final String email; + final int hasProfileImage; + final String profileChangedAt; + final int avatarColor; + const UserEntityData({ + required this.id, + required this.name, + required this.email, + required this.hasProfileImage, + required this.profileChangedAt, + required this.avatarColor, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['email'] = Variable(email); + map['has_profile_image'] = Variable(hasProfileImage); + map['profile_changed_at'] = Variable(profileChangedAt); + map['avatar_color'] = Variable(avatarColor); + return map; + } + + factory UserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + email: serializer.fromJson(json['email']), + hasProfileImage: serializer.fromJson(json['hasProfileImage']), + profileChangedAt: serializer.fromJson(json['profileChangedAt']), + avatarColor: serializer.fromJson(json['avatarColor']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'email': serializer.toJson(email), + 'hasProfileImage': serializer.toJson(hasProfileImage), + 'profileChangedAt': serializer.toJson(profileChangedAt), + 'avatarColor': serializer.toJson(avatarColor), + }; + } + + UserEntityData copyWith({ + String? id, + String? name, + String? email, + int? hasProfileImage, + String? profileChangedAt, + int? avatarColor, + }) => UserEntityData( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + ); + UserEntityData copyWithCompanion(UserEntityCompanion data) { + return UserEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + email: data.email.present ? data.email.value : this.email, + hasProfileImage: data.hasProfileImage.present + ? data.hasProfileImage.value + : this.hasProfileImage, + profileChangedAt: data.profileChangedAt.present + ? data.profileChangedAt.value + : this.profileChangedAt, + avatarColor: data.avatarColor.present + ? data.avatarColor.value + : this.avatarColor, + ); + } + + @override + String toString() { + return (StringBuffer('UserEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + email, + hasProfileImage, + profileChangedAt, + avatarColor, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserEntityData && + other.id == this.id && + other.name == this.name && + other.email == this.email && + other.hasProfileImage == this.hasProfileImage && + other.profileChangedAt == this.profileChangedAt && + other.avatarColor == this.avatarColor); +} + +class UserEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value email; + final Value hasProfileImage; + final Value profileChangedAt; + final Value avatarColor; + const UserEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.email = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + }); + UserEntityCompanion.insert({ + required String id, + required String name, + required String email, + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + }) : id = Value(id), + name = Value(name), + email = Value(email); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? email, + Expression? hasProfileImage, + Expression? profileChangedAt, + Expression? avatarColor, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (email != null) 'email': email, + if (hasProfileImage != null) 'has_profile_image': hasProfileImage, + if (profileChangedAt != null) 'profile_changed_at': profileChangedAt, + if (avatarColor != null) 'avatar_color': avatarColor, + }); + } + + UserEntityCompanion copyWith({ + Value? id, + Value? name, + Value? email, + Value? hasProfileImage, + Value? profileChangedAt, + Value? avatarColor, + }) { + return UserEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (email.present) { + map['email'] = Variable(email.value); + } + if (hasProfileImage.present) { + map['has_profile_image'] = Variable(hasProfileImage.value); + } + if (profileChangedAt.present) { + map['profile_changed_at'] = Variable(profileChangedAt.value); + } + if (avatarColor.present) { + map['avatar_color'] = Variable(avatarColor.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor') + ..write(')')) + .toString(); + } +} + +class RemoteAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn durationMs = GeneratedColumn( + 'duration_ms', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0 CHECK (is_favorite IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL REFERENCES user_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn localDateTime = GeneratedColumn( + 'local_date_time', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn thumbHash = GeneratedColumn( + 'thumb_hash', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn uploadedAt = GeneratedColumn( + 'uploaded_at', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn livePhotoVideoId = GeneratedColumn( + 'live_photo_video_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn visibility = GeneratedColumn( + 'visibility', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn stackId = GeneratedColumn( + 'stack_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn libraryId = GeneratedColumn( + 'library_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn isEdited = GeneratedColumn( + 'is_edited', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0 CHECK (is_edited IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationMs, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + uploadedAt, + livePhotoVideoId, + visibility, + stackId, + libraryId, + isEdited, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_asset_entity'; + @override + Set get $primaryKey => {id}; + @override + RemoteAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationMs: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_ms'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + )!, + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_favorite'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + localDateTime: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}local_date_time'], + ), + thumbHash: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumb_hash'], + ), + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}deleted_at'], + ), + uploadedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}uploaded_at'], + ), + livePhotoVideoId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}live_photo_video_id'], + ), + visibility: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}visibility'], + )!, + stackId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}stack_id'], + ), + libraryId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}library_id'], + ), + isEdited: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_edited'], + )!, + ); + } + + @override + RemoteAssetEntity createAlias(String alias) { + return RemoteAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class RemoteAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final String createdAt; + final String updatedAt; + final int? width; + final int? height; + final int? durationMs; + final String id; + final String checksum; + final int isFavorite; + final String ownerId; + final String? localDateTime; + final String? thumbHash; + final String? deletedAt; + final String? uploadedAt; + final String? livePhotoVideoId; + final int visibility; + final String? stackId; + final String? libraryId; + final int isEdited; + const RemoteAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationMs, + required this.id, + required this.checksum, + required this.isFavorite, + required this.ownerId, + this.localDateTime, + this.thumbHash, + this.deletedAt, + this.uploadedAt, + this.livePhotoVideoId, + required this.visibility, + this.stackId, + this.libraryId, + required this.isEdited, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationMs != null) { + map['duration_ms'] = Variable(durationMs); + } + map['id'] = Variable(id); + map['checksum'] = Variable(checksum); + map['is_favorite'] = Variable(isFavorite); + map['owner_id'] = Variable(ownerId); + if (!nullToAbsent || localDateTime != null) { + map['local_date_time'] = Variable(localDateTime); + } + if (!nullToAbsent || thumbHash != null) { + map['thumb_hash'] = Variable(thumbHash); + } + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + if (!nullToAbsent || uploadedAt != null) { + map['uploaded_at'] = Variable(uploadedAt); + } + if (!nullToAbsent || livePhotoVideoId != null) { + map['live_photo_video_id'] = Variable(livePhotoVideoId); + } + map['visibility'] = Variable(visibility); + if (!nullToAbsent || stackId != null) { + map['stack_id'] = Variable(stackId); + } + if (!nullToAbsent || libraryId != null) { + map['library_id'] = Variable(libraryId); + } + map['is_edited'] = Variable(isEdited); + return map; + } + + factory RemoteAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationMs: serializer.fromJson(json['durationMs']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + ownerId: serializer.fromJson(json['ownerId']), + localDateTime: serializer.fromJson(json['localDateTime']), + thumbHash: serializer.fromJson(json['thumbHash']), + deletedAt: serializer.fromJson(json['deletedAt']), + uploadedAt: serializer.fromJson(json['uploadedAt']), + livePhotoVideoId: serializer.fromJson(json['livePhotoVideoId']), + visibility: serializer.fromJson(json['visibility']), + stackId: serializer.fromJson(json['stackId']), + libraryId: serializer.fromJson(json['libraryId']), + isEdited: serializer.fromJson(json['isEdited']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationMs': serializer.toJson(durationMs), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'ownerId': serializer.toJson(ownerId), + 'localDateTime': serializer.toJson(localDateTime), + 'thumbHash': serializer.toJson(thumbHash), + 'deletedAt': serializer.toJson(deletedAt), + 'uploadedAt': serializer.toJson(uploadedAt), + 'livePhotoVideoId': serializer.toJson(livePhotoVideoId), + 'visibility': serializer.toJson(visibility), + 'stackId': serializer.toJson(stackId), + 'libraryId': serializer.toJson(libraryId), + 'isEdited': serializer.toJson(isEdited), + }; + } + + RemoteAssetEntityData copyWith({ + String? name, + int? type, + String? createdAt, + String? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationMs = const Value.absent(), + String? id, + String? checksum, + int? isFavorite, + String? ownerId, + Value localDateTime = const Value.absent(), + Value thumbHash = const Value.absent(), + Value deletedAt = const Value.absent(), + Value uploadedAt = const Value.absent(), + Value livePhotoVideoId = const Value.absent(), + int? visibility, + Value stackId = const Value.absent(), + Value libraryId = const Value.absent(), + int? isEdited, + }) => RemoteAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationMs: durationMs.present ? durationMs.value : this.durationMs, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime.present + ? localDateTime.value + : this.localDateTime, + thumbHash: thumbHash.present ? thumbHash.value : this.thumbHash, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + uploadedAt: uploadedAt.present ? uploadedAt.value : this.uploadedAt, + livePhotoVideoId: livePhotoVideoId.present + ? livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId.present ? stackId.value : this.stackId, + libraryId: libraryId.present ? libraryId.value : this.libraryId, + isEdited: isEdited ?? this.isEdited, + ); + RemoteAssetEntityData copyWithCompanion(RemoteAssetEntityCompanion data) { + return RemoteAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationMs: data.durationMs.present + ? data.durationMs.value + : this.durationMs, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + localDateTime: data.localDateTime.present + ? data.localDateTime.value + : this.localDateTime, + thumbHash: data.thumbHash.present ? data.thumbHash.value : this.thumbHash, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + uploadedAt: data.uploadedAt.present + ? data.uploadedAt.value + : this.uploadedAt, + livePhotoVideoId: data.livePhotoVideoId.present + ? data.livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: data.visibility.present + ? data.visibility.value + : this.visibility, + stackId: data.stackId.present ? data.stackId.value : this.stackId, + libraryId: data.libraryId.present ? data.libraryId.value : this.libraryId, + isEdited: data.isEdited.present ? data.isEdited.value : this.isEdited, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationMs: $durationMs, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('uploadedAt: $uploadedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId, ') + ..write('libraryId: $libraryId, ') + ..write('isEdited: $isEdited') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationMs, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + uploadedAt, + livePhotoVideoId, + visibility, + stackId, + libraryId, + isEdited, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationMs == this.durationMs && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.ownerId == this.ownerId && + other.localDateTime == this.localDateTime && + other.thumbHash == this.thumbHash && + other.deletedAt == this.deletedAt && + other.uploadedAt == this.uploadedAt && + other.livePhotoVideoId == this.livePhotoVideoId && + other.visibility == this.visibility && + other.stackId == this.stackId && + other.libraryId == this.libraryId && + other.isEdited == this.isEdited); +} + +class RemoteAssetEntityCompanion + extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationMs; + final Value id; + final Value checksum; + final Value isFavorite; + final Value ownerId; + final Value localDateTime; + final Value thumbHash; + final Value deletedAt; + final Value uploadedAt; + final Value livePhotoVideoId; + final Value visibility; + final Value stackId; + final Value libraryId; + final Value isEdited; + const RemoteAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationMs = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.ownerId = const Value.absent(), + this.localDateTime = const Value.absent(), + this.thumbHash = const Value.absent(), + this.deletedAt = const Value.absent(), + this.uploadedAt = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + this.visibility = const Value.absent(), + this.stackId = const Value.absent(), + this.libraryId = const Value.absent(), + this.isEdited = const Value.absent(), + }); + RemoteAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationMs = const Value.absent(), + required String id, + required String checksum, + this.isFavorite = const Value.absent(), + required String ownerId, + this.localDateTime = const Value.absent(), + this.thumbHash = const Value.absent(), + this.deletedAt = const Value.absent(), + this.uploadedAt = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + required int visibility, + this.stackId = const Value.absent(), + this.libraryId = const Value.absent(), + this.isEdited = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id), + checksum = Value(checksum), + ownerId = Value(ownerId), + visibility = Value(visibility); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationMs, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? ownerId, + Expression? localDateTime, + Expression? thumbHash, + Expression? deletedAt, + Expression? uploadedAt, + Expression? livePhotoVideoId, + Expression? visibility, + Expression? stackId, + Expression? libraryId, + Expression? isEdited, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationMs != null) 'duration_ms': durationMs, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (ownerId != null) 'owner_id': ownerId, + if (localDateTime != null) 'local_date_time': localDateTime, + if (thumbHash != null) 'thumb_hash': thumbHash, + if (deletedAt != null) 'deleted_at': deletedAt, + if (uploadedAt != null) 'uploaded_at': uploadedAt, + if (livePhotoVideoId != null) 'live_photo_video_id': livePhotoVideoId, + if (visibility != null) 'visibility': visibility, + if (stackId != null) 'stack_id': stackId, + if (libraryId != null) 'library_id': libraryId, + if (isEdited != null) 'is_edited': isEdited, + }); + } + + RemoteAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationMs, + Value? id, + Value? checksum, + Value? isFavorite, + Value? ownerId, + Value? localDateTime, + Value? thumbHash, + Value? deletedAt, + Value? uploadedAt, + Value? livePhotoVideoId, + Value? visibility, + Value? stackId, + Value? libraryId, + Value? isEdited, + }) { + return RemoteAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationMs: durationMs ?? this.durationMs, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime ?? this.localDateTime, + thumbHash: thumbHash ?? this.thumbHash, + deletedAt: deletedAt ?? this.deletedAt, + uploadedAt: uploadedAt ?? this.uploadedAt, + livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId ?? this.stackId, + libraryId: libraryId ?? this.libraryId, + isEdited: isEdited ?? this.isEdited, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationMs.present) { + map['duration_ms'] = Variable(durationMs.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (localDateTime.present) { + map['local_date_time'] = Variable(localDateTime.value); + } + if (thumbHash.present) { + map['thumb_hash'] = Variable(thumbHash.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + if (uploadedAt.present) { + map['uploaded_at'] = Variable(uploadedAt.value); + } + if (livePhotoVideoId.present) { + map['live_photo_video_id'] = Variable(livePhotoVideoId.value); + } + if (visibility.present) { + map['visibility'] = Variable(visibility.value); + } + if (stackId.present) { + map['stack_id'] = Variable(stackId.value); + } + if (libraryId.present) { + map['library_id'] = Variable(libraryId.value); + } + if (isEdited.present) { + map['is_edited'] = Variable(isEdited.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationMs: $durationMs, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('uploadedAt: $uploadedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId, ') + ..write('libraryId: $libraryId, ') + ..write('isEdited: $isEdited') + ..write(')')) + .toString(); + } +} + +class StackEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + StackEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL REFERENCES user_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn primaryAssetId = GeneratedColumn( + 'primary_asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + primaryAssetId, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'stack_entity'; + @override + Set get $primaryKey => {id}; + @override + StackEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return StackEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + primaryAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}primary_asset_id'], + )!, + ); + } + + @override + StackEntity createAlias(String alias) { + return StackEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class StackEntityData extends DataClass implements Insertable { + final String id; + final String createdAt; + final String updatedAt; + final String ownerId; + final String primaryAssetId; + const StackEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.primaryAssetId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + map['primary_asset_id'] = Variable(primaryAssetId); + return map; + } + + factory StackEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return StackEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + primaryAssetId: serializer.fromJson(json['primaryAssetId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'primaryAssetId': serializer.toJson(primaryAssetId), + }; + } + + StackEntityData copyWith({ + String? id, + String? createdAt, + String? updatedAt, + String? ownerId, + String? primaryAssetId, + }) => StackEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + StackEntityData copyWithCompanion(StackEntityCompanion data) { + return StackEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + primaryAssetId: data.primaryAssetId.present + ? data.primaryAssetId.value + : this.primaryAssetId, + ); + } + + @override + String toString() { + return (StringBuffer('StackEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(id, createdAt, updatedAt, ownerId, primaryAssetId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is StackEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.primaryAssetId == this.primaryAssetId); +} + +class StackEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value primaryAssetId; + const StackEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.primaryAssetId = const Value.absent(), + }); + StackEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + required String primaryAssetId, + }) : id = Value(id), + ownerId = Value(ownerId), + primaryAssetId = Value(primaryAssetId); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? primaryAssetId, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (primaryAssetId != null) 'primary_asset_id': primaryAssetId, + }); + } + + StackEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? primaryAssetId, + }) { + return StackEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (primaryAssetId.present) { + map['primary_asset_id'] = Variable(primaryAssetId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('StackEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } +} + +class LocalAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn durationMs = GeneratedColumn( + 'duration_ms', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0 CHECK (is_favorite IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn iCloudId = GeneratedColumn( + 'i_cloud_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn adjustmentTime = GeneratedColumn( + 'adjustment_time', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn playbackStyle = GeneratedColumn( + 'playback_style', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0', + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationMs, + id, + checksum, + isFavorite, + orientation, + iCloudId, + adjustmentTime, + latitude, + longitude, + playbackStyle, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_asset_entity'; + @override + Set get $primaryKey => {id}; + @override + LocalAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationMs: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_ms'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_favorite'], + )!, + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}orientation'], + )!, + iCloudId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}i_cloud_id'], + ), + adjustmentTime: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}adjustment_time'], + ), + latitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + playbackStyle: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}playback_style'], + )!, + ); + } + + @override + LocalAssetEntity createAlias(String alias) { + return LocalAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class LocalAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final String createdAt; + final String updatedAt; + final int? width; + final int? height; + final int? durationMs; + final String id; + final String? checksum; + final int isFavorite; + final int orientation; + final String? iCloudId; + final String? adjustmentTime; + final double? latitude; + final double? longitude; + final int playbackStyle; + const LocalAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationMs, + required this.id, + this.checksum, + required this.isFavorite, + required this.orientation, + this.iCloudId, + this.adjustmentTime, + this.latitude, + this.longitude, + required this.playbackStyle, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationMs != null) { + map['duration_ms'] = Variable(durationMs); + } + map['id'] = Variable(id); + if (!nullToAbsent || checksum != null) { + map['checksum'] = Variable(checksum); + } + map['is_favorite'] = Variable(isFavorite); + map['orientation'] = Variable(orientation); + if (!nullToAbsent || iCloudId != null) { + map['i_cloud_id'] = Variable(iCloudId); + } + if (!nullToAbsent || adjustmentTime != null) { + map['adjustment_time'] = Variable(adjustmentTime); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = Variable(longitude); + } + map['playback_style'] = Variable(playbackStyle); + return map; + } + + factory LocalAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationMs: serializer.fromJson(json['durationMs']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + orientation: serializer.fromJson(json['orientation']), + iCloudId: serializer.fromJson(json['iCloudId']), + adjustmentTime: serializer.fromJson(json['adjustmentTime']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + playbackStyle: serializer.fromJson(json['playbackStyle']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationMs': serializer.toJson(durationMs), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'orientation': serializer.toJson(orientation), + 'iCloudId': serializer.toJson(iCloudId), + 'adjustmentTime': serializer.toJson(adjustmentTime), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + 'playbackStyle': serializer.toJson(playbackStyle), + }; + } + + LocalAssetEntityData copyWith({ + String? name, + int? type, + String? createdAt, + String? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationMs = const Value.absent(), + String? id, + Value checksum = const Value.absent(), + int? isFavorite, + int? orientation, + Value iCloudId = const Value.absent(), + Value adjustmentTime = const Value.absent(), + Value latitude = const Value.absent(), + Value longitude = const Value.absent(), + int? playbackStyle, + }) => LocalAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationMs: durationMs.present ? durationMs.value : this.durationMs, + id: id ?? this.id, + checksum: checksum.present ? checksum.value : this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + iCloudId: iCloudId.present ? iCloudId.value : this.iCloudId, + adjustmentTime: adjustmentTime.present + ? adjustmentTime.value + : this.adjustmentTime, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + playbackStyle: playbackStyle ?? this.playbackStyle, + ); + LocalAssetEntityData copyWithCompanion(LocalAssetEntityCompanion data) { + return LocalAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationMs: data.durationMs.present + ? data.durationMs.value + : this.durationMs, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + iCloudId: data.iCloudId.present ? data.iCloudId.value : this.iCloudId, + adjustmentTime: data.adjustmentTime.present + ? data.adjustmentTime.value + : this.adjustmentTime, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + playbackStyle: data.playbackStyle.present + ? data.playbackStyle.value + : this.playbackStyle, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationMs: $durationMs, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation, ') + ..write('iCloudId: $iCloudId, ') + ..write('adjustmentTime: $adjustmentTime, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('playbackStyle: $playbackStyle') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationMs, + id, + checksum, + isFavorite, + orientation, + iCloudId, + adjustmentTime, + latitude, + longitude, + playbackStyle, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationMs == this.durationMs && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.orientation == this.orientation && + other.iCloudId == this.iCloudId && + other.adjustmentTime == this.adjustmentTime && + other.latitude == this.latitude && + other.longitude == this.longitude && + other.playbackStyle == this.playbackStyle); +} + +class LocalAssetEntityCompanion extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationMs; + final Value id; + final Value checksum; + final Value isFavorite; + final Value orientation; + final Value iCloudId; + final Value adjustmentTime; + final Value latitude; + final Value longitude; + final Value playbackStyle; + const LocalAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationMs = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + this.iCloudId = const Value.absent(), + this.adjustmentTime = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.playbackStyle = const Value.absent(), + }); + LocalAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationMs = const Value.absent(), + required String id, + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + this.iCloudId = const Value.absent(), + this.adjustmentTime = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.playbackStyle = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationMs, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? orientation, + Expression? iCloudId, + Expression? adjustmentTime, + Expression? latitude, + Expression? longitude, + Expression? playbackStyle, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationMs != null) 'duration_ms': durationMs, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (orientation != null) 'orientation': orientation, + if (iCloudId != null) 'i_cloud_id': iCloudId, + if (adjustmentTime != null) 'adjustment_time': adjustmentTime, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + if (playbackStyle != null) 'playback_style': playbackStyle, + }); + } + + LocalAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationMs, + Value? id, + Value? checksum, + Value? isFavorite, + Value? orientation, + Value? iCloudId, + Value? adjustmentTime, + Value? latitude, + Value? longitude, + Value? playbackStyle, + }) { + return LocalAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationMs: durationMs ?? this.durationMs, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + iCloudId: iCloudId ?? this.iCloudId, + adjustmentTime: adjustmentTime ?? this.adjustmentTime, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + playbackStyle: playbackStyle ?? this.playbackStyle, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationMs.present) { + map['duration_ms'] = Variable(durationMs.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + if (iCloudId.present) { + map['i_cloud_id'] = Variable(iCloudId.value); + } + if (adjustmentTime.present) { + map['adjustment_time'] = Variable(adjustmentTime.value); + } + if (latitude.present) { + map['latitude'] = Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = Variable(longitude.value); + } + if (playbackStyle.present) { + map['playback_style'] = Variable(playbackStyle.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationMs: $durationMs, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation, ') + ..write('iCloudId: $iCloudId, ') + ..write('adjustmentTime: $adjustmentTime, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('playbackStyle: $playbackStyle') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT \'\'', + defaultValue: const CustomExpression('\'\''), + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn thumbnailAssetId = GeneratedColumn( + 'thumbnail_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: + 'NULL REFERENCES remote_asset_entity(id)ON DELETE SET NULL', + ); + late final GeneratedColumn isActivityEnabled = GeneratedColumn( + 'is_activity_enabled', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: + 'NOT NULL DEFAULT 1 CHECK (is_activity_enabled IN (0, 1))', + defaultValue: const CustomExpression('1'), + ); + late final GeneratedColumn order = GeneratedColumn( + 'order', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + @override + List get $columns => [ + id, + name, + description, + createdAt, + updatedAt, + thumbnailAssetId, + isActivityEnabled, + order, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_entity'; + @override + Set get $primaryKey => {id}; + @override + RemoteAlbumEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + thumbnailAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumbnail_asset_id'], + ), + isActivityEnabled: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_activity_enabled'], + )!, + order: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}order'], + )!, + ); + } + + @override + RemoteAlbumEntity createAlias(String alias) { + return RemoteAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class RemoteAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String description; + final String createdAt; + final String updatedAt; + final String? thumbnailAssetId; + final int isActivityEnabled; + final int order; + const RemoteAlbumEntityData({ + required this.id, + required this.name, + required this.description, + required this.createdAt, + required this.updatedAt, + this.thumbnailAssetId, + required this.isActivityEnabled, + required this.order, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['description'] = Variable(description); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || thumbnailAssetId != null) { + map['thumbnail_asset_id'] = Variable(thumbnailAssetId); + } + map['is_activity_enabled'] = Variable(isActivityEnabled); + map['order'] = Variable(order); + return map; + } + + factory RemoteAlbumEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + description: serializer.fromJson(json['description']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + thumbnailAssetId: serializer.fromJson(json['thumbnailAssetId']), + isActivityEnabled: serializer.fromJson(json['isActivityEnabled']), + order: serializer.fromJson(json['order']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'description': serializer.toJson(description), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'thumbnailAssetId': serializer.toJson(thumbnailAssetId), + 'isActivityEnabled': serializer.toJson(isActivityEnabled), + 'order': serializer.toJson(order), + }; + } + + RemoteAlbumEntityData copyWith({ + String? id, + String? name, + String? description, + String? createdAt, + String? updatedAt, + Value thumbnailAssetId = const Value.absent(), + int? isActivityEnabled, + int? order, + }) => RemoteAlbumEntityData( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + thumbnailAssetId: thumbnailAssetId.present + ? thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + RemoteAlbumEntityData copyWithCompanion(RemoteAlbumEntityCompanion data) { + return RemoteAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + description: data.description.present + ? data.description.value + : this.description, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + thumbnailAssetId: data.thumbnailAssetId.present + ? data.thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: data.isActivityEnabled.present + ? data.isActivityEnabled.value + : this.isActivityEnabled, + order: data.order.present ? data.order.value : this.order, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + description, + createdAt, + updatedAt, + thumbnailAssetId, + isActivityEnabled, + order, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.description == this.description && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.thumbnailAssetId == this.thumbnailAssetId && + other.isActivityEnabled == this.isActivityEnabled && + other.order == this.order); +} + +class RemoteAlbumEntityCompanion + extends UpdateCompanion { + final Value id; + final Value name; + final Value description; + final Value createdAt; + final Value updatedAt; + final Value thumbnailAssetId; + final Value isActivityEnabled; + final Value order; + const RemoteAlbumEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.description = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + this.order = const Value.absent(), + }); + RemoteAlbumEntityCompanion.insert({ + required String id, + required String name, + this.description = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + required int order, + }) : id = Value(id), + name = Value(name), + order = Value(order); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? description, + Expression? createdAt, + Expression? updatedAt, + Expression? thumbnailAssetId, + Expression? isActivityEnabled, + Expression? order, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (description != null) 'description': description, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (thumbnailAssetId != null) 'thumbnail_asset_id': thumbnailAssetId, + if (isActivityEnabled != null) 'is_activity_enabled': isActivityEnabled, + if (order != null) 'order': order, + }); + } + + RemoteAlbumEntityCompanion copyWith({ + Value? id, + Value? name, + Value? description, + Value? createdAt, + Value? updatedAt, + Value? thumbnailAssetId, + Value? isActivityEnabled, + Value? order, + }) { + return RemoteAlbumEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + thumbnailAssetId: thumbnailAssetId ?? this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (thumbnailAssetId.present) { + map['thumbnail_asset_id'] = Variable(thumbnailAssetId.value); + } + if (isActivityEnabled.present) { + map['is_activity_enabled'] = Variable(isActivityEnabled.value); + } + if (order.present) { + map['order'] = Variable(order.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } +} + +class LocalAlbumEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAlbumEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn backupSelection = GeneratedColumn( + 'backup_selection', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn isIosSharedAlbum = GeneratedColumn( + 'is_ios_shared_album', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: + 'NOT NULL DEFAULT 0 CHECK (is_ios_shared_album IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn linkedRemoteAlbumId = + GeneratedColumn( + 'linked_remote_album_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: + 'NULL REFERENCES remote_album_entity(id)ON DELETE SET NULL', + ); + late final GeneratedColumn marker = GeneratedColumn( + 'marker', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL CHECK (marker IN (0, 1))', + ); + @override + List get $columns => [ + id, + name, + updatedAt, + backupSelection, + isIosSharedAlbum, + linkedRemoteAlbumId, + marker, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_entity'; + @override + Set get $primaryKey => {id}; + @override + LocalAlbumEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAlbumEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + backupSelection: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}backup_selection'], + )!, + isIosSharedAlbum: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_ios_shared_album'], + )!, + linkedRemoteAlbumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}linked_remote_album_id'], + ), + marker: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}marker'], + ), + ); + } + + @override + LocalAlbumEntity createAlias(String alias) { + return LocalAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class LocalAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String updatedAt; + final int backupSelection; + final int isIosSharedAlbum; + final String? linkedRemoteAlbumId; + final int? marker; + const LocalAlbumEntityData({ + required this.id, + required this.name, + required this.updatedAt, + required this.backupSelection, + required this.isIosSharedAlbum, + this.linkedRemoteAlbumId, + this.marker, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['updated_at'] = Variable(updatedAt); + map['backup_selection'] = Variable(backupSelection); + map['is_ios_shared_album'] = Variable(isIosSharedAlbum); + if (!nullToAbsent || linkedRemoteAlbumId != null) { + map['linked_remote_album_id'] = Variable(linkedRemoteAlbumId); + } + if (!nullToAbsent || marker != null) { + map['marker'] = Variable(marker); + } + return map; + } + + factory LocalAlbumEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + updatedAt: serializer.fromJson(json['updatedAt']), + backupSelection: serializer.fromJson(json['backupSelection']), + isIosSharedAlbum: serializer.fromJson(json['isIosSharedAlbum']), + linkedRemoteAlbumId: serializer.fromJson( + json['linkedRemoteAlbumId'], + ), + marker: serializer.fromJson(json['marker']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'updatedAt': serializer.toJson(updatedAt), + 'backupSelection': serializer.toJson(backupSelection), + 'isIosSharedAlbum': serializer.toJson(isIosSharedAlbum), + 'linkedRemoteAlbumId': serializer.toJson(linkedRemoteAlbumId), + 'marker': serializer.toJson(marker), + }; + } + + LocalAlbumEntityData copyWith({ + String? id, + String? name, + String? updatedAt, + int? backupSelection, + int? isIosSharedAlbum, + Value linkedRemoteAlbumId = const Value.absent(), + Value marker = const Value.absent(), + }) => LocalAlbumEntityData( + id: id ?? this.id, + name: name ?? this.name, + updatedAt: updatedAt ?? this.updatedAt, + backupSelection: backupSelection ?? this.backupSelection, + isIosSharedAlbum: isIosSharedAlbum ?? this.isIosSharedAlbum, + linkedRemoteAlbumId: linkedRemoteAlbumId.present + ? linkedRemoteAlbumId.value + : this.linkedRemoteAlbumId, + marker: marker.present ? marker.value : this.marker, + ); + LocalAlbumEntityData copyWithCompanion(LocalAlbumEntityCompanion data) { + return LocalAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + backupSelection: data.backupSelection.present + ? data.backupSelection.value + : this.backupSelection, + isIosSharedAlbum: data.isIosSharedAlbum.present + ? data.isIosSharedAlbum.value + : this.isIosSharedAlbum, + linkedRemoteAlbumId: data.linkedRemoteAlbumId.present + ? data.linkedRemoteAlbumId.value + : this.linkedRemoteAlbumId, + marker: data.marker.present ? data.marker.value : this.marker, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('updatedAt: $updatedAt, ') + ..write('backupSelection: $backupSelection, ') + ..write('isIosSharedAlbum: $isIosSharedAlbum, ') + ..write('linkedRemoteAlbumId: $linkedRemoteAlbumId, ') + ..write('marker: $marker') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + updatedAt, + backupSelection, + isIosSharedAlbum, + linkedRemoteAlbumId, + marker, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.updatedAt == this.updatedAt && + other.backupSelection == this.backupSelection && + other.isIosSharedAlbum == this.isIosSharedAlbum && + other.linkedRemoteAlbumId == this.linkedRemoteAlbumId && + other.marker == this.marker); +} + +class LocalAlbumEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value updatedAt; + final Value backupSelection; + final Value isIosSharedAlbum; + final Value linkedRemoteAlbumId; + final Value marker; + const LocalAlbumEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.updatedAt = const Value.absent(), + this.backupSelection = const Value.absent(), + this.isIosSharedAlbum = const Value.absent(), + this.linkedRemoteAlbumId = const Value.absent(), + this.marker = const Value.absent(), + }); + LocalAlbumEntityCompanion.insert({ + required String id, + required String name, + this.updatedAt = const Value.absent(), + required int backupSelection, + this.isIosSharedAlbum = const Value.absent(), + this.linkedRemoteAlbumId = const Value.absent(), + this.marker = const Value.absent(), + }) : id = Value(id), + name = Value(name), + backupSelection = Value(backupSelection); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? updatedAt, + Expression? backupSelection, + Expression? isIosSharedAlbum, + Expression? linkedRemoteAlbumId, + Expression? marker, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (updatedAt != null) 'updated_at': updatedAt, + if (backupSelection != null) 'backup_selection': backupSelection, + if (isIosSharedAlbum != null) 'is_ios_shared_album': isIosSharedAlbum, + if (linkedRemoteAlbumId != null) + 'linked_remote_album_id': linkedRemoteAlbumId, + if (marker != null) 'marker': marker, + }); + } + + LocalAlbumEntityCompanion copyWith({ + Value? id, + Value? name, + Value? updatedAt, + Value? backupSelection, + Value? isIosSharedAlbum, + Value? linkedRemoteAlbumId, + Value? marker, + }) { + return LocalAlbumEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + updatedAt: updatedAt ?? this.updatedAt, + backupSelection: backupSelection ?? this.backupSelection, + isIosSharedAlbum: isIosSharedAlbum ?? this.isIosSharedAlbum, + linkedRemoteAlbumId: linkedRemoteAlbumId ?? this.linkedRemoteAlbumId, + marker: marker ?? this.marker, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (backupSelection.present) { + map['backup_selection'] = Variable(backupSelection.value); + } + if (isIosSharedAlbum.present) { + map['is_ios_shared_album'] = Variable(isIosSharedAlbum.value); + } + if (linkedRemoteAlbumId.present) { + map['linked_remote_album_id'] = Variable( + linkedRemoteAlbumId.value, + ); + } + if (marker.present) { + map['marker'] = Variable(marker.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('updatedAt: $updatedAt, ') + ..write('backupSelection: $backupSelection, ') + ..write('isIosSharedAlbum: $isIosSharedAlbum, ') + ..write('linkedRemoteAlbumId: $linkedRemoteAlbumId, ') + ..write('marker: $marker') + ..write(')')) + .toString(); + } +} + +class LocalAlbumAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAlbumAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES local_asset_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES local_album_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn marker = GeneratedColumn( + 'marker', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL CHECK (marker IN (0, 1))', + ); + @override + List get $columns => [assetId, albumId, marker]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_asset_entity'; + @override + Set get $primaryKey => {assetId, albumId}; + @override + LocalAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + marker: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}marker'], + ), + ); + } + + @override + LocalAlbumAssetEntity createAlias(String alias) { + return LocalAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const [ + 'PRIMARY KEY(asset_id, album_id)', + ]; + @override + bool get dontWriteConstraints => true; +} + +class LocalAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + final int? marker; + const LocalAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + this.marker, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['album_id'] = Variable(albumId); + if (!nullToAbsent || marker != null) { + map['marker'] = Variable(marker); + } + return map; + } + + factory LocalAlbumAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + marker: serializer.fromJson(json['marker']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + 'marker': serializer.toJson(marker), + }; + } + + LocalAlbumAssetEntityData copyWith({ + String? assetId, + String? albumId, + Value marker = const Value.absent(), + }) => LocalAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + marker: marker.present ? marker.value : this.marker, + ); + LocalAlbumAssetEntityData copyWithCompanion( + LocalAlbumAssetEntityCompanion data, + ) { + return LocalAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + marker: data.marker.present ? data.marker.value : this.marker, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId, ') + ..write('marker: $marker') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId, marker); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId && + other.marker == this.marker); +} + +class LocalAlbumAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value albumId; + final Value marker; + const LocalAlbumAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.albumId = const Value.absent(), + this.marker = const Value.absent(), + }); + LocalAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + this.marker = const Value.absent(), + }) : assetId = Value(assetId), + albumId = Value(albumId); + static Insertable custom({ + Expression? assetId, + Expression? albumId, + Expression? marker, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + if (marker != null) 'marker': marker, + }); + } + + LocalAlbumAssetEntityCompanion copyWith({ + Value? assetId, + Value? albumId, + Value? marker, + }) { + return LocalAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + marker: marker ?? this.marker, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (marker.present) { + map['marker'] = Variable(marker.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId, ') + ..write('marker: $marker') + ..write(')')) + .toString(); + } +} + +class AuthUserEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AuthUserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn email = GeneratedColumn( + 'email', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn isAdmin = GeneratedColumn( + 'is_admin', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0 CHECK (is_admin IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn hasProfileImage = GeneratedColumn( + 'has_profile_image', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: + 'NOT NULL DEFAULT 0 CHECK (has_profile_image IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn profileChangedAt = GeneratedColumn( + 'profile_changed_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn avatarColor = GeneratedColumn( + 'avatar_color', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn quotaSizeInBytes = GeneratedColumn( + 'quota_size_in_bytes', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn quotaUsageInBytes = GeneratedColumn( + 'quota_usage_in_bytes', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn pinCode = GeneratedColumn( + 'pin_code', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + @override + List get $columns => [ + id, + name, + email, + isAdmin, + hasProfileImage, + profileChangedAt, + avatarColor, + quotaSizeInBytes, + quotaUsageInBytes, + pinCode, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'auth_user_entity'; + @override + Set get $primaryKey => {id}; + @override + AuthUserEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AuthUserEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + email: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}email'], + )!, + isAdmin: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_admin'], + )!, + hasProfileImage: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}has_profile_image'], + )!, + profileChangedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}profile_changed_at'], + )!, + avatarColor: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}avatar_color'], + )!, + quotaSizeInBytes: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}quota_size_in_bytes'], + )!, + quotaUsageInBytes: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}quota_usage_in_bytes'], + )!, + pinCode: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}pin_code'], + ), + ); + } + + @override + AuthUserEntity createAlias(String alias) { + return AuthUserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class AuthUserEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String email; + final int isAdmin; + final int hasProfileImage; + final String profileChangedAt; + final int avatarColor; + final int quotaSizeInBytes; + final int quotaUsageInBytes; + final String? pinCode; + const AuthUserEntityData({ + required this.id, + required this.name, + required this.email, + required this.isAdmin, + required this.hasProfileImage, + required this.profileChangedAt, + required this.avatarColor, + required this.quotaSizeInBytes, + required this.quotaUsageInBytes, + this.pinCode, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['email'] = Variable(email); + map['is_admin'] = Variable(isAdmin); + map['has_profile_image'] = Variable(hasProfileImage); + map['profile_changed_at'] = Variable(profileChangedAt); + map['avatar_color'] = Variable(avatarColor); + map['quota_size_in_bytes'] = Variable(quotaSizeInBytes); + map['quota_usage_in_bytes'] = Variable(quotaUsageInBytes); + if (!nullToAbsent || pinCode != null) { + map['pin_code'] = Variable(pinCode); + } + return map; + } + + factory AuthUserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AuthUserEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + email: serializer.fromJson(json['email']), + isAdmin: serializer.fromJson(json['isAdmin']), + hasProfileImage: serializer.fromJson(json['hasProfileImage']), + profileChangedAt: serializer.fromJson(json['profileChangedAt']), + avatarColor: serializer.fromJson(json['avatarColor']), + quotaSizeInBytes: serializer.fromJson(json['quotaSizeInBytes']), + quotaUsageInBytes: serializer.fromJson(json['quotaUsageInBytes']), + pinCode: serializer.fromJson(json['pinCode']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'email': serializer.toJson(email), + 'isAdmin': serializer.toJson(isAdmin), + 'hasProfileImage': serializer.toJson(hasProfileImage), + 'profileChangedAt': serializer.toJson(profileChangedAt), + 'avatarColor': serializer.toJson(avatarColor), + 'quotaSizeInBytes': serializer.toJson(quotaSizeInBytes), + 'quotaUsageInBytes': serializer.toJson(quotaUsageInBytes), + 'pinCode': serializer.toJson(pinCode), + }; + } + + AuthUserEntityData copyWith({ + String? id, + String? name, + String? email, + int? isAdmin, + int? hasProfileImage, + String? profileChangedAt, + int? avatarColor, + int? quotaSizeInBytes, + int? quotaUsageInBytes, + Value pinCode = const Value.absent(), + }) => AuthUserEntityData( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + isAdmin: isAdmin ?? this.isAdmin, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + quotaSizeInBytes: quotaSizeInBytes ?? this.quotaSizeInBytes, + quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes, + pinCode: pinCode.present ? pinCode.value : this.pinCode, + ); + AuthUserEntityData copyWithCompanion(AuthUserEntityCompanion data) { + return AuthUserEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + email: data.email.present ? data.email.value : this.email, + isAdmin: data.isAdmin.present ? data.isAdmin.value : this.isAdmin, + hasProfileImage: data.hasProfileImage.present + ? data.hasProfileImage.value + : this.hasProfileImage, + profileChangedAt: data.profileChangedAt.present + ? data.profileChangedAt.value + : this.profileChangedAt, + avatarColor: data.avatarColor.present + ? data.avatarColor.value + : this.avatarColor, + quotaSizeInBytes: data.quotaSizeInBytes.present + ? data.quotaSizeInBytes.value + : this.quotaSizeInBytes, + quotaUsageInBytes: data.quotaUsageInBytes.present + ? data.quotaUsageInBytes.value + : this.quotaUsageInBytes, + pinCode: data.pinCode.present ? data.pinCode.value : this.pinCode, + ); + } + + @override + String toString() { + return (StringBuffer('AuthUserEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('isAdmin: $isAdmin, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor, ') + ..write('quotaSizeInBytes: $quotaSizeInBytes, ') + ..write('quotaUsageInBytes: $quotaUsageInBytes, ') + ..write('pinCode: $pinCode') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + email, + isAdmin, + hasProfileImage, + profileChangedAt, + avatarColor, + quotaSizeInBytes, + quotaUsageInBytes, + pinCode, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AuthUserEntityData && + other.id == this.id && + other.name == this.name && + other.email == this.email && + other.isAdmin == this.isAdmin && + other.hasProfileImage == this.hasProfileImage && + other.profileChangedAt == this.profileChangedAt && + other.avatarColor == this.avatarColor && + other.quotaSizeInBytes == this.quotaSizeInBytes && + other.quotaUsageInBytes == this.quotaUsageInBytes && + other.pinCode == this.pinCode); +} + +class AuthUserEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value email; + final Value isAdmin; + final Value hasProfileImage; + final Value profileChangedAt; + final Value avatarColor; + final Value quotaSizeInBytes; + final Value quotaUsageInBytes; + final Value pinCode; + const AuthUserEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.email = const Value.absent(), + this.isAdmin = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + this.quotaSizeInBytes = const Value.absent(), + this.quotaUsageInBytes = const Value.absent(), + this.pinCode = const Value.absent(), + }); + AuthUserEntityCompanion.insert({ + required String id, + required String name, + required String email, + this.isAdmin = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + required int avatarColor, + this.quotaSizeInBytes = const Value.absent(), + this.quotaUsageInBytes = const Value.absent(), + this.pinCode = const Value.absent(), + }) : id = Value(id), + name = Value(name), + email = Value(email), + avatarColor = Value(avatarColor); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? email, + Expression? isAdmin, + Expression? hasProfileImage, + Expression? profileChangedAt, + Expression? avatarColor, + Expression? quotaSizeInBytes, + Expression? quotaUsageInBytes, + Expression? pinCode, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (email != null) 'email': email, + if (isAdmin != null) 'is_admin': isAdmin, + if (hasProfileImage != null) 'has_profile_image': hasProfileImage, + if (profileChangedAt != null) 'profile_changed_at': profileChangedAt, + if (avatarColor != null) 'avatar_color': avatarColor, + if (quotaSizeInBytes != null) 'quota_size_in_bytes': quotaSizeInBytes, + if (quotaUsageInBytes != null) 'quota_usage_in_bytes': quotaUsageInBytes, + if (pinCode != null) 'pin_code': pinCode, + }); + } + + AuthUserEntityCompanion copyWith({ + Value? id, + Value? name, + Value? email, + Value? isAdmin, + Value? hasProfileImage, + Value? profileChangedAt, + Value? avatarColor, + Value? quotaSizeInBytes, + Value? quotaUsageInBytes, + Value? pinCode, + }) { + return AuthUserEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + isAdmin: isAdmin ?? this.isAdmin, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + quotaSizeInBytes: quotaSizeInBytes ?? this.quotaSizeInBytes, + quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes, + pinCode: pinCode ?? this.pinCode, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (email.present) { + map['email'] = Variable(email.value); + } + if (isAdmin.present) { + map['is_admin'] = Variable(isAdmin.value); + } + if (hasProfileImage.present) { + map['has_profile_image'] = Variable(hasProfileImage.value); + } + if (profileChangedAt.present) { + map['profile_changed_at'] = Variable(profileChangedAt.value); + } + if (avatarColor.present) { + map['avatar_color'] = Variable(avatarColor.value); + } + if (quotaSizeInBytes.present) { + map['quota_size_in_bytes'] = Variable(quotaSizeInBytes.value); + } + if (quotaUsageInBytes.present) { + map['quota_usage_in_bytes'] = Variable(quotaUsageInBytes.value); + } + if (pinCode.present) { + map['pin_code'] = Variable(pinCode.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AuthUserEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('isAdmin: $isAdmin, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor, ') + ..write('quotaSizeInBytes: $quotaSizeInBytes, ') + ..write('quotaUsageInBytes: $quotaUsageInBytes, ') + ..write('pinCode: $pinCode') + ..write(')')) + .toString(); + } +} + +class UserMetadataEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UserMetadataEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL REFERENCES user_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn key = GeneratedColumn( + 'key', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn value = + GeneratedColumn( + 'value', + aliasedName, + false, + type: DriftSqlType.blob, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + @override + List get $columns => [userId, key, value]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_metadata_entity'; + @override + Set get $primaryKey => {userId, key}; + @override + UserMetadataEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserMetadataEntityData( + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + key: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}key'], + )!, + value: attachedDatabase.typeMapping.read( + DriftSqlType.blob, + data['${effectivePrefix}value'], + )!, + ); + } + + @override + UserMetadataEntity createAlias(String alias) { + return UserMetadataEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(user_id, "key")']; + @override + bool get dontWriteConstraints => true; +} + +class UserMetadataEntityData extends DataClass + implements Insertable { + final String userId; + final int key; + final i2.Uint8List value; + const UserMetadataEntityData({ + required this.userId, + required this.key, + required this.value, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['user_id'] = Variable(userId); + map['key'] = Variable(key); + map['value'] = Variable(value); + return map; + } + + factory UserMetadataEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserMetadataEntityData( + userId: serializer.fromJson(json['userId']), + key: serializer.fromJson(json['key']), + value: serializer.fromJson(json['value']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'userId': serializer.toJson(userId), + 'key': serializer.toJson(key), + 'value': serializer.toJson(value), + }; + } + + UserMetadataEntityData copyWith({ + String? userId, + int? key, + i2.Uint8List? value, + }) => UserMetadataEntityData( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + UserMetadataEntityData copyWithCompanion(UserMetadataEntityCompanion data) { + return UserMetadataEntityData( + userId: data.userId.present ? data.userId.value : this.userId, + key: data.key.present ? data.key.value : this.key, + value: data.value.present ? data.value.value : this.value, + ); + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityData(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(userId, key, $driftBlobEquality.hash(value)); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserMetadataEntityData && + other.userId == this.userId && + other.key == this.key && + $driftBlobEquality.equals(other.value, this.value)); +} + +class UserMetadataEntityCompanion + extends UpdateCompanion { + final Value userId; + final Value key; + final Value value; + const UserMetadataEntityCompanion({ + this.userId = const Value.absent(), + this.key = const Value.absent(), + this.value = const Value.absent(), + }); + UserMetadataEntityCompanion.insert({ + required String userId, + required int key, + required i2.Uint8List value, + }) : userId = Value(userId), + key = Value(key), + value = Value(value); + static Insertable custom({ + Expression? userId, + Expression? key, + Expression? value, + }) { + return RawValuesInsertable({ + if (userId != null) 'user_id': userId, + if (key != null) 'key': key, + if (value != null) 'value': value, + }); + } + + UserMetadataEntityCompanion copyWith({ + Value? userId, + Value? key, + Value? value, + }) { + return UserMetadataEntityCompanion( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (key.present) { + map['key'] = Variable(key.value); + } + if (value.present) { + map['value'] = Variable(value.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityCompanion(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } +} + +class PartnerEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PartnerEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn sharedById = GeneratedColumn( + 'shared_by_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL REFERENCES user_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn sharedWithId = GeneratedColumn( + 'shared_with_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL REFERENCES user_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn inTimeline = GeneratedColumn( + 'in_timeline', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0 CHECK (in_timeline IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [sharedById, sharedWithId, inTimeline]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'partner_entity'; + @override + Set get $primaryKey => {sharedById, sharedWithId}; + @override + PartnerEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PartnerEntityData( + sharedById: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}shared_by_id'], + )!, + sharedWithId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}shared_with_id'], + )!, + inTimeline: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}in_timeline'], + )!, + ); + } + + @override + PartnerEntity createAlias(String alias) { + return PartnerEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const [ + 'PRIMARY KEY(shared_by_id, shared_with_id)', + ]; + @override + bool get dontWriteConstraints => true; +} + +class PartnerEntityData extends DataClass + implements Insertable { + final String sharedById; + final String sharedWithId; + final int inTimeline; + const PartnerEntityData({ + required this.sharedById, + required this.sharedWithId, + required this.inTimeline, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['shared_by_id'] = Variable(sharedById); + map['shared_with_id'] = Variable(sharedWithId); + map['in_timeline'] = Variable(inTimeline); + return map; + } + + factory PartnerEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PartnerEntityData( + sharedById: serializer.fromJson(json['sharedById']), + sharedWithId: serializer.fromJson(json['sharedWithId']), + inTimeline: serializer.fromJson(json['inTimeline']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'sharedById': serializer.toJson(sharedById), + 'sharedWithId': serializer.toJson(sharedWithId), + 'inTimeline': serializer.toJson(inTimeline), + }; + } + + PartnerEntityData copyWith({ + String? sharedById, + String? sharedWithId, + int? inTimeline, + }) => PartnerEntityData( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + PartnerEntityData copyWithCompanion(PartnerEntityCompanion data) { + return PartnerEntityData( + sharedById: data.sharedById.present + ? data.sharedById.value + : this.sharedById, + sharedWithId: data.sharedWithId.present + ? data.sharedWithId.value + : this.sharedWithId, + inTimeline: data.inTimeline.present + ? data.inTimeline.value + : this.inTimeline, + ); + } + + @override + String toString() { + return (StringBuffer('PartnerEntityData(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(sharedById, sharedWithId, inTimeline); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PartnerEntityData && + other.sharedById == this.sharedById && + other.sharedWithId == this.sharedWithId && + other.inTimeline == this.inTimeline); +} + +class PartnerEntityCompanion extends UpdateCompanion { + final Value sharedById; + final Value sharedWithId; + final Value inTimeline; + const PartnerEntityCompanion({ + this.sharedById = const Value.absent(), + this.sharedWithId = const Value.absent(), + this.inTimeline = const Value.absent(), + }); + PartnerEntityCompanion.insert({ + required String sharedById, + required String sharedWithId, + this.inTimeline = const Value.absent(), + }) : sharedById = Value(sharedById), + sharedWithId = Value(sharedWithId); + static Insertable custom({ + Expression? sharedById, + Expression? sharedWithId, + Expression? inTimeline, + }) { + return RawValuesInsertable({ + if (sharedById != null) 'shared_by_id': sharedById, + if (sharedWithId != null) 'shared_with_id': sharedWithId, + if (inTimeline != null) 'in_timeline': inTimeline, + }); + } + + PartnerEntityCompanion copyWith({ + Value? sharedById, + Value? sharedWithId, + Value? inTimeline, + }) { + return PartnerEntityCompanion( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (sharedById.present) { + map['shared_by_id'] = Variable(sharedById.value); + } + if (sharedWithId.present) { + map['shared_with_id'] = Variable(sharedWithId.value); + } + if (inTimeline.present) { + map['in_timeline'] = Variable(inTimeline.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PartnerEntityCompanion(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } +} + +class RemoteExifEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteExifEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES remote_asset_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn city = GeneratedColumn( + 'city', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn state = GeneratedColumn( + 'state', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn country = GeneratedColumn( + 'country', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn dateTimeOriginal = GeneratedColumn( + 'date_time_original', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn exposureTime = GeneratedColumn( + 'exposure_time', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn fNumber = GeneratedColumn( + 'f_number', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn fileSize = GeneratedColumn( + 'file_size', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn focalLength = GeneratedColumn( + 'focal_length', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn iso = GeneratedColumn( + 'iso', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn make = GeneratedColumn( + 'make', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn model = GeneratedColumn( + 'model', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn lens = GeneratedColumn( + 'lens', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn timeZone = GeneratedColumn( + 'time_zone', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn rating = GeneratedColumn( + 'rating', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn projectionType = GeneratedColumn( + 'projection_type', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + @override + List get $columns => [ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_exif_entity'; + @override + Set get $primaryKey => {assetId}; + @override + RemoteExifEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteExifEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + city: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}city'], + ), + state: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}state'], + ), + country: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}country'], + ), + dateTimeOriginal: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}date_time_original'], + ), + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + exposureTime: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}exposure_time'], + ), + fNumber: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}f_number'], + ), + fileSize: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}file_size'], + ), + focalLength: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}focal_length'], + ), + latitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + iso: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}iso'], + ), + make: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}make'], + ), + model: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}model'], + ), + lens: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}lens'], + ), + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}orientation'], + ), + timeZone: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}time_zone'], + ), + rating: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}rating'], + ), + projectionType: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}projection_type'], + ), + ); + } + + @override + RemoteExifEntity createAlias(String alias) { + return RemoteExifEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(asset_id)']; + @override + bool get dontWriteConstraints => true; +} + +class RemoteExifEntityData extends DataClass + implements Insertable { + final String assetId; + final String? city; + final String? state; + final String? country; + final String? dateTimeOriginal; + final String? description; + final int? height; + final int? width; + final String? exposureTime; + final double? fNumber; + final int? fileSize; + final double? focalLength; + final double? latitude; + final double? longitude; + final int? iso; + final String? make; + final String? model; + final String? lens; + final String? orientation; + final String? timeZone; + final int? rating; + final String? projectionType; + const RemoteExifEntityData({ + required this.assetId, + this.city, + this.state, + this.country, + this.dateTimeOriginal, + this.description, + this.height, + this.width, + this.exposureTime, + this.fNumber, + this.fileSize, + this.focalLength, + this.latitude, + this.longitude, + this.iso, + this.make, + this.model, + this.lens, + this.orientation, + this.timeZone, + this.rating, + this.projectionType, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || city != null) { + map['city'] = Variable(city); + } + if (!nullToAbsent || state != null) { + map['state'] = Variable(state); + } + if (!nullToAbsent || country != null) { + map['country'] = Variable(country); + } + if (!nullToAbsent || dateTimeOriginal != null) { + map['date_time_original'] = Variable(dateTimeOriginal); + } + if (!nullToAbsent || description != null) { + map['description'] = Variable(description); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || exposureTime != null) { + map['exposure_time'] = Variable(exposureTime); + } + if (!nullToAbsent || fNumber != null) { + map['f_number'] = Variable(fNumber); + } + if (!nullToAbsent || fileSize != null) { + map['file_size'] = Variable(fileSize); + } + if (!nullToAbsent || focalLength != null) { + map['focal_length'] = Variable(focalLength); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = Variable(longitude); + } + if (!nullToAbsent || iso != null) { + map['iso'] = Variable(iso); + } + if (!nullToAbsent || make != null) { + map['make'] = Variable(make); + } + if (!nullToAbsent || model != null) { + map['model'] = Variable(model); + } + if (!nullToAbsent || lens != null) { + map['lens'] = Variable(lens); + } + if (!nullToAbsent || orientation != null) { + map['orientation'] = Variable(orientation); + } + if (!nullToAbsent || timeZone != null) { + map['time_zone'] = Variable(timeZone); + } + if (!nullToAbsent || rating != null) { + map['rating'] = Variable(rating); + } + if (!nullToAbsent || projectionType != null) { + map['projection_type'] = Variable(projectionType); + } + return map; + } + + factory RemoteExifEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteExifEntityData( + assetId: serializer.fromJson(json['assetId']), + city: serializer.fromJson(json['city']), + state: serializer.fromJson(json['state']), + country: serializer.fromJson(json['country']), + dateTimeOriginal: serializer.fromJson(json['dateTimeOriginal']), + description: serializer.fromJson(json['description']), + height: serializer.fromJson(json['height']), + width: serializer.fromJson(json['width']), + exposureTime: serializer.fromJson(json['exposureTime']), + fNumber: serializer.fromJson(json['fNumber']), + fileSize: serializer.fromJson(json['fileSize']), + focalLength: serializer.fromJson(json['focalLength']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + iso: serializer.fromJson(json['iso']), + make: serializer.fromJson(json['make']), + model: serializer.fromJson(json['model']), + lens: serializer.fromJson(json['lens']), + orientation: serializer.fromJson(json['orientation']), + timeZone: serializer.fromJson(json['timeZone']), + rating: serializer.fromJson(json['rating']), + projectionType: serializer.fromJson(json['projectionType']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'city': serializer.toJson(city), + 'state': serializer.toJson(state), + 'country': serializer.toJson(country), + 'dateTimeOriginal': serializer.toJson(dateTimeOriginal), + 'description': serializer.toJson(description), + 'height': serializer.toJson(height), + 'width': serializer.toJson(width), + 'exposureTime': serializer.toJson(exposureTime), + 'fNumber': serializer.toJson(fNumber), + 'fileSize': serializer.toJson(fileSize), + 'focalLength': serializer.toJson(focalLength), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + 'iso': serializer.toJson(iso), + 'make': serializer.toJson(make), + 'model': serializer.toJson(model), + 'lens': serializer.toJson(lens), + 'orientation': serializer.toJson(orientation), + 'timeZone': serializer.toJson(timeZone), + 'rating': serializer.toJson(rating), + 'projectionType': serializer.toJson(projectionType), + }; + } + + RemoteExifEntityData copyWith({ + String? assetId, + Value city = const Value.absent(), + Value state = const Value.absent(), + Value country = const Value.absent(), + Value dateTimeOriginal = const Value.absent(), + Value description = const Value.absent(), + Value height = const Value.absent(), + Value width = const Value.absent(), + Value exposureTime = const Value.absent(), + Value fNumber = const Value.absent(), + Value fileSize = const Value.absent(), + Value focalLength = const Value.absent(), + Value latitude = const Value.absent(), + Value longitude = const Value.absent(), + Value iso = const Value.absent(), + Value make = const Value.absent(), + Value model = const Value.absent(), + Value lens = const Value.absent(), + Value orientation = const Value.absent(), + Value timeZone = const Value.absent(), + Value rating = const Value.absent(), + Value projectionType = const Value.absent(), + }) => RemoteExifEntityData( + assetId: assetId ?? this.assetId, + city: city.present ? city.value : this.city, + state: state.present ? state.value : this.state, + country: country.present ? country.value : this.country, + dateTimeOriginal: dateTimeOriginal.present + ? dateTimeOriginal.value + : this.dateTimeOriginal, + description: description.present ? description.value : this.description, + height: height.present ? height.value : this.height, + width: width.present ? width.value : this.width, + exposureTime: exposureTime.present ? exposureTime.value : this.exposureTime, + fNumber: fNumber.present ? fNumber.value : this.fNumber, + fileSize: fileSize.present ? fileSize.value : this.fileSize, + focalLength: focalLength.present ? focalLength.value : this.focalLength, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + iso: iso.present ? iso.value : this.iso, + make: make.present ? make.value : this.make, + model: model.present ? model.value : this.model, + lens: lens.present ? lens.value : this.lens, + orientation: orientation.present ? orientation.value : this.orientation, + timeZone: timeZone.present ? timeZone.value : this.timeZone, + rating: rating.present ? rating.value : this.rating, + projectionType: projectionType.present + ? projectionType.value + : this.projectionType, + ); + RemoteExifEntityData copyWithCompanion(RemoteExifEntityCompanion data) { + return RemoteExifEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + city: data.city.present ? data.city.value : this.city, + state: data.state.present ? data.state.value : this.state, + country: data.country.present ? data.country.value : this.country, + dateTimeOriginal: data.dateTimeOriginal.present + ? data.dateTimeOriginal.value + : this.dateTimeOriginal, + description: data.description.present + ? data.description.value + : this.description, + height: data.height.present ? data.height.value : this.height, + width: data.width.present ? data.width.value : this.width, + exposureTime: data.exposureTime.present + ? data.exposureTime.value + : this.exposureTime, + fNumber: data.fNumber.present ? data.fNumber.value : this.fNumber, + fileSize: data.fileSize.present ? data.fileSize.value : this.fileSize, + focalLength: data.focalLength.present + ? data.focalLength.value + : this.focalLength, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + iso: data.iso.present ? data.iso.value : this.iso, + make: data.make.present ? data.make.value : this.make, + model: data.model.present ? data.model.value : this.model, + lens: data.lens.present ? data.lens.value : this.lens, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + timeZone: data.timeZone.present ? data.timeZone.value : this.timeZone, + rating: data.rating.present ? data.rating.value : this.rating, + projectionType: data.projectionType.present + ? data.projectionType.value + : this.projectionType, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityData(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hashAll([ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteExifEntityData && + other.assetId == this.assetId && + other.city == this.city && + other.state == this.state && + other.country == this.country && + other.dateTimeOriginal == this.dateTimeOriginal && + other.description == this.description && + other.height == this.height && + other.width == this.width && + other.exposureTime == this.exposureTime && + other.fNumber == this.fNumber && + other.fileSize == this.fileSize && + other.focalLength == this.focalLength && + other.latitude == this.latitude && + other.longitude == this.longitude && + other.iso == this.iso && + other.make == this.make && + other.model == this.model && + other.lens == this.lens && + other.orientation == this.orientation && + other.timeZone == this.timeZone && + other.rating == this.rating && + other.projectionType == this.projectionType); +} + +class RemoteExifEntityCompanion extends UpdateCompanion { + final Value assetId; + final Value city; + final Value state; + final Value country; + final Value dateTimeOriginal; + final Value description; + final Value height; + final Value width; + final Value exposureTime; + final Value fNumber; + final Value fileSize; + final Value focalLength; + final Value latitude; + final Value longitude; + final Value iso; + final Value make; + final Value model; + final Value lens; + final Value orientation; + final Value timeZone; + final Value rating; + final Value projectionType; + const RemoteExifEntityCompanion({ + this.assetId = const Value.absent(), + this.city = const Value.absent(), + this.state = const Value.absent(), + this.country = const Value.absent(), + this.dateTimeOriginal = const Value.absent(), + this.description = const Value.absent(), + this.height = const Value.absent(), + this.width = const Value.absent(), + this.exposureTime = const Value.absent(), + this.fNumber = const Value.absent(), + this.fileSize = const Value.absent(), + this.focalLength = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.iso = const Value.absent(), + this.make = const Value.absent(), + this.model = const Value.absent(), + this.lens = const Value.absent(), + this.orientation = const Value.absent(), + this.timeZone = const Value.absent(), + this.rating = const Value.absent(), + this.projectionType = const Value.absent(), + }); + RemoteExifEntityCompanion.insert({ + required String assetId, + this.city = const Value.absent(), + this.state = const Value.absent(), + this.country = const Value.absent(), + this.dateTimeOriginal = const Value.absent(), + this.description = const Value.absent(), + this.height = const Value.absent(), + this.width = const Value.absent(), + this.exposureTime = const Value.absent(), + this.fNumber = const Value.absent(), + this.fileSize = const Value.absent(), + this.focalLength = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.iso = const Value.absent(), + this.make = const Value.absent(), + this.model = const Value.absent(), + this.lens = const Value.absent(), + this.orientation = const Value.absent(), + this.timeZone = const Value.absent(), + this.rating = const Value.absent(), + this.projectionType = const Value.absent(), + }) : assetId = Value(assetId); + static Insertable custom({ + Expression? assetId, + Expression? city, + Expression? state, + Expression? country, + Expression? dateTimeOriginal, + Expression? description, + Expression? height, + Expression? width, + Expression? exposureTime, + Expression? fNumber, + Expression? fileSize, + Expression? focalLength, + Expression? latitude, + Expression? longitude, + Expression? iso, + Expression? make, + Expression? model, + Expression? lens, + Expression? orientation, + Expression? timeZone, + Expression? rating, + Expression? projectionType, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (city != null) 'city': city, + if (state != null) 'state': state, + if (country != null) 'country': country, + if (dateTimeOriginal != null) 'date_time_original': dateTimeOriginal, + if (description != null) 'description': description, + if (height != null) 'height': height, + if (width != null) 'width': width, + if (exposureTime != null) 'exposure_time': exposureTime, + if (fNumber != null) 'f_number': fNumber, + if (fileSize != null) 'file_size': fileSize, + if (focalLength != null) 'focal_length': focalLength, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + if (iso != null) 'iso': iso, + if (make != null) 'make': make, + if (model != null) 'model': model, + if (lens != null) 'lens': lens, + if (orientation != null) 'orientation': orientation, + if (timeZone != null) 'time_zone': timeZone, + if (rating != null) 'rating': rating, + if (projectionType != null) 'projection_type': projectionType, + }); + } + + RemoteExifEntityCompanion copyWith({ + Value? assetId, + Value? city, + Value? state, + Value? country, + Value? dateTimeOriginal, + Value? description, + Value? height, + Value? width, + Value? exposureTime, + Value? fNumber, + Value? fileSize, + Value? focalLength, + Value? latitude, + Value? longitude, + Value? iso, + Value? make, + Value? model, + Value? lens, + Value? orientation, + Value? timeZone, + Value? rating, + Value? projectionType, + }) { + return RemoteExifEntityCompanion( + assetId: assetId ?? this.assetId, + city: city ?? this.city, + state: state ?? this.state, + country: country ?? this.country, + dateTimeOriginal: dateTimeOriginal ?? this.dateTimeOriginal, + description: description ?? this.description, + height: height ?? this.height, + width: width ?? this.width, + exposureTime: exposureTime ?? this.exposureTime, + fNumber: fNumber ?? this.fNumber, + fileSize: fileSize ?? this.fileSize, + focalLength: focalLength ?? this.focalLength, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + iso: iso ?? this.iso, + make: make ?? this.make, + model: model ?? this.model, + lens: lens ?? this.lens, + orientation: orientation ?? this.orientation, + timeZone: timeZone ?? this.timeZone, + rating: rating ?? this.rating, + projectionType: projectionType ?? this.projectionType, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (city.present) { + map['city'] = Variable(city.value); + } + if (state.present) { + map['state'] = Variable(state.value); + } + if (country.present) { + map['country'] = Variable(country.value); + } + if (dateTimeOriginal.present) { + map['date_time_original'] = Variable(dateTimeOriginal.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (exposureTime.present) { + map['exposure_time'] = Variable(exposureTime.value); + } + if (fNumber.present) { + map['f_number'] = Variable(fNumber.value); + } + if (fileSize.present) { + map['file_size'] = Variable(fileSize.value); + } + if (focalLength.present) { + map['focal_length'] = Variable(focalLength.value); + } + if (latitude.present) { + map['latitude'] = Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = Variable(longitude.value); + } + if (iso.present) { + map['iso'] = Variable(iso.value); + } + if (make.present) { + map['make'] = Variable(make.value); + } + if (model.present) { + map['model'] = Variable(model.value); + } + if (lens.present) { + map['lens'] = Variable(lens.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + if (timeZone.present) { + map['time_zone'] = Variable(timeZone.value); + } + if (rating.present) { + map['rating'] = Variable(rating.value); + } + if (projectionType.present) { + map['projection_type'] = Variable(projectionType.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES remote_asset_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES remote_album_entity(id)ON DELETE CASCADE', + ); + @override + List get $columns => [assetId, albumId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_asset_entity'; + @override + Set get $primaryKey => {assetId, albumId}; + @override + RemoteAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + ); + } + + @override + RemoteAlbumAssetEntity createAlias(String alias) { + return RemoteAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const [ + 'PRIMARY KEY(asset_id, album_id)', + ]; + @override + bool get dontWriteConstraints => true; +} + +class RemoteAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + const RemoteAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['album_id'] = Variable(albumId); + return map; + } + + factory RemoteAlbumAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + }; + } + + RemoteAlbumAssetEntityData copyWith({String? assetId, String? albumId}) => + RemoteAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + RemoteAlbumAssetEntityData copyWithCompanion( + RemoteAlbumAssetEntityCompanion data, + ) { + return RemoteAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId); +} + +class RemoteAlbumAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value albumId; + const RemoteAlbumAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.albumId = const Value.absent(), + }); + RemoteAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + }) : assetId = Value(assetId), + albumId = Value(albumId); + static Insertable custom({ + Expression? assetId, + Expression? albumId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + }); + } + + RemoteAlbumAssetEntityCompanion copyWith({ + Value? assetId, + Value? albumId, + }) { + return RemoteAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumUserEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumUserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES remote_album_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL REFERENCES user_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn role = GeneratedColumn( + 'role', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + @override + List get $columns => [albumId, userId, role]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_user_entity'; + @override + Set get $primaryKey => {albumId, userId}; + @override + RemoteAlbumUserEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumUserEntityData( + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + role: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}role'], + )!, + ); + } + + @override + RemoteAlbumUserEntity createAlias(String alias) { + return RemoteAlbumUserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const [ + 'PRIMARY KEY(album_id, user_id)', + ]; + @override + bool get dontWriteConstraints => true; +} + +class RemoteAlbumUserEntityData extends DataClass + implements Insertable { + final String albumId; + final String userId; + final int role; + const RemoteAlbumUserEntityData({ + required this.albumId, + required this.userId, + required this.role, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['album_id'] = Variable(albumId); + map['user_id'] = Variable(userId); + map['role'] = Variable(role); + return map; + } + + factory RemoteAlbumUserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumUserEntityData( + albumId: serializer.fromJson(json['albumId']), + userId: serializer.fromJson(json['userId']), + role: serializer.fromJson(json['role']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'albumId': serializer.toJson(albumId), + 'userId': serializer.toJson(userId), + 'role': serializer.toJson(role), + }; + } + + RemoteAlbumUserEntityData copyWith({ + String? albumId, + String? userId, + int? role, + }) => RemoteAlbumUserEntityData( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + RemoteAlbumUserEntityData copyWithCompanion( + RemoteAlbumUserEntityCompanion data, + ) { + return RemoteAlbumUserEntityData( + albumId: data.albumId.present ? data.albumId.value : this.albumId, + userId: data.userId.present ? data.userId.value : this.userId, + role: data.role.present ? data.role.value : this.role, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityData(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(albumId, userId, role); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumUserEntityData && + other.albumId == this.albumId && + other.userId == this.userId && + other.role == this.role); +} + +class RemoteAlbumUserEntityCompanion + extends UpdateCompanion { + final Value albumId; + final Value userId; + final Value role; + const RemoteAlbumUserEntityCompanion({ + this.albumId = const Value.absent(), + this.userId = const Value.absent(), + this.role = const Value.absent(), + }); + RemoteAlbumUserEntityCompanion.insert({ + required String albumId, + required String userId, + required int role, + }) : albumId = Value(albumId), + userId = Value(userId), + role = Value(role); + static Insertable custom({ + Expression? albumId, + Expression? userId, + Expression? role, + }) { + return RawValuesInsertable({ + if (albumId != null) 'album_id': albumId, + if (userId != null) 'user_id': userId, + if (role != null) 'role': role, + }); + } + + RemoteAlbumUserEntityCompanion copyWith({ + Value? albumId, + Value? userId, + Value? role, + }) { + return RemoteAlbumUserEntityCompanion( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (role.present) { + map['role'] = Variable(role.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityCompanion(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } +} + +class RemoteAssetCloudIdEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAssetCloudIdEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES remote_asset_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn cloudId = GeneratedColumn( + 'cloud_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn adjustmentTime = GeneratedColumn( + 'adjustment_time', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + @override + List get $columns => [ + assetId, + cloudId, + createdAt, + adjustmentTime, + latitude, + longitude, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_asset_cloud_id_entity'; + @override + Set get $primaryKey => {assetId}; + @override + RemoteAssetCloudIdEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAssetCloudIdEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + cloudId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}cloud_id'], + ), + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}created_at'], + ), + adjustmentTime: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}adjustment_time'], + ), + latitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + ); + } + + @override + RemoteAssetCloudIdEntity createAlias(String alias) { + return RemoteAssetCloudIdEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(asset_id)']; + @override + bool get dontWriteConstraints => true; +} + +class RemoteAssetCloudIdEntityData extends DataClass + implements Insertable { + final String assetId; + final String? cloudId; + final String? createdAt; + final String? adjustmentTime; + final double? latitude; + final double? longitude; + const RemoteAssetCloudIdEntityData({ + required this.assetId, + this.cloudId, + this.createdAt, + this.adjustmentTime, + this.latitude, + this.longitude, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || cloudId != null) { + map['cloud_id'] = Variable(cloudId); + } + if (!nullToAbsent || createdAt != null) { + map['created_at'] = Variable(createdAt); + } + if (!nullToAbsent || adjustmentTime != null) { + map['adjustment_time'] = Variable(adjustmentTime); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = Variable(longitude); + } + return map; + } + + factory RemoteAssetCloudIdEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAssetCloudIdEntityData( + assetId: serializer.fromJson(json['assetId']), + cloudId: serializer.fromJson(json['cloudId']), + createdAt: serializer.fromJson(json['createdAt']), + adjustmentTime: serializer.fromJson(json['adjustmentTime']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'cloudId': serializer.toJson(cloudId), + 'createdAt': serializer.toJson(createdAt), + 'adjustmentTime': serializer.toJson(adjustmentTime), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + }; + } + + RemoteAssetCloudIdEntityData copyWith({ + String? assetId, + Value cloudId = const Value.absent(), + Value createdAt = const Value.absent(), + Value adjustmentTime = const Value.absent(), + Value latitude = const Value.absent(), + Value longitude = const Value.absent(), + }) => RemoteAssetCloudIdEntityData( + assetId: assetId ?? this.assetId, + cloudId: cloudId.present ? cloudId.value : this.cloudId, + createdAt: createdAt.present ? createdAt.value : this.createdAt, + adjustmentTime: adjustmentTime.present + ? adjustmentTime.value + : this.adjustmentTime, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + ); + RemoteAssetCloudIdEntityData copyWithCompanion( + RemoteAssetCloudIdEntityCompanion data, + ) { + return RemoteAssetCloudIdEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + cloudId: data.cloudId.present ? data.cloudId.value : this.cloudId, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + adjustmentTime: data.adjustmentTime.present + ? data.adjustmentTime.value + : this.adjustmentTime, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAssetCloudIdEntityData(') + ..write('assetId: $assetId, ') + ..write('cloudId: $cloudId, ') + ..write('createdAt: $createdAt, ') + ..write('adjustmentTime: $adjustmentTime, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + assetId, + cloudId, + createdAt, + adjustmentTime, + latitude, + longitude, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAssetCloudIdEntityData && + other.assetId == this.assetId && + other.cloudId == this.cloudId && + other.createdAt == this.createdAt && + other.adjustmentTime == this.adjustmentTime && + other.latitude == this.latitude && + other.longitude == this.longitude); +} + +class RemoteAssetCloudIdEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value cloudId; + final Value createdAt; + final Value adjustmentTime; + final Value latitude; + final Value longitude; + const RemoteAssetCloudIdEntityCompanion({ + this.assetId = const Value.absent(), + this.cloudId = const Value.absent(), + this.createdAt = const Value.absent(), + this.adjustmentTime = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + }); + RemoteAssetCloudIdEntityCompanion.insert({ + required String assetId, + this.cloudId = const Value.absent(), + this.createdAt = const Value.absent(), + this.adjustmentTime = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + }) : assetId = Value(assetId); + static Insertable custom({ + Expression? assetId, + Expression? cloudId, + Expression? createdAt, + Expression? adjustmentTime, + Expression? latitude, + Expression? longitude, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (cloudId != null) 'cloud_id': cloudId, + if (createdAt != null) 'created_at': createdAt, + if (adjustmentTime != null) 'adjustment_time': adjustmentTime, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + }); + } + + RemoteAssetCloudIdEntityCompanion copyWith({ + Value? assetId, + Value? cloudId, + Value? createdAt, + Value? adjustmentTime, + Value? latitude, + Value? longitude, + }) { + return RemoteAssetCloudIdEntityCompanion( + assetId: assetId ?? this.assetId, + cloudId: cloudId ?? this.cloudId, + createdAt: createdAt ?? this.createdAt, + adjustmentTime: adjustmentTime ?? this.adjustmentTime, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (cloudId.present) { + map['cloud_id'] = Variable(cloudId.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (adjustmentTime.present) { + map['adjustment_time'] = Variable(adjustmentTime.value); + } + if (latitude.present) { + map['latitude'] = Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = Variable(longitude.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAssetCloudIdEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('cloudId: $cloudId, ') + ..write('createdAt: $createdAt, ') + ..write('adjustmentTime: $adjustmentTime, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude') + ..write(')')) + .toString(); + } +} + +class MemoryEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MemoryEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL REFERENCES user_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn data = GeneratedColumn( + 'data', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn isSaved = GeneratedColumn( + 'is_saved', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0 CHECK (is_saved IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn memoryAt = GeneratedColumn( + 'memory_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn seenAt = GeneratedColumn( + 'seen_at', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn showAt = GeneratedColumn( + 'show_at', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn hideAt = GeneratedColumn( + 'hide_at', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_entity'; + @override + Set get $primaryKey => {id}; + @override + MemoryEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MemoryEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}deleted_at'], + ), + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + data: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}data'], + )!, + isSaved: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_saved'], + )!, + memoryAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}memory_at'], + )!, + seenAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}seen_at'], + ), + showAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}show_at'], + ), + hideAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}hide_at'], + ), + ); + } + + @override + MemoryEntity createAlias(String alias) { + return MemoryEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class MemoryEntityData extends DataClass + implements Insertable { + final String id; + final String createdAt; + final String updatedAt; + final String? deletedAt; + final String ownerId; + final int type; + final String data; + final int isSaved; + final String memoryAt; + final String? seenAt; + final String? showAt; + final String? hideAt; + const MemoryEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + this.deletedAt, + required this.ownerId, + required this.type, + required this.data, + required this.isSaved, + required this.memoryAt, + this.seenAt, + this.showAt, + this.hideAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + map['owner_id'] = Variable(ownerId); + map['type'] = Variable(type); + map['data'] = Variable(data); + map['is_saved'] = Variable(isSaved); + map['memory_at'] = Variable(memoryAt); + if (!nullToAbsent || seenAt != null) { + map['seen_at'] = Variable(seenAt); + } + if (!nullToAbsent || showAt != null) { + map['show_at'] = Variable(showAt); + } + if (!nullToAbsent || hideAt != null) { + map['hide_at'] = Variable(hideAt); + } + return map; + } + + factory MemoryEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MemoryEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + deletedAt: serializer.fromJson(json['deletedAt']), + ownerId: serializer.fromJson(json['ownerId']), + type: serializer.fromJson(json['type']), + data: serializer.fromJson(json['data']), + isSaved: serializer.fromJson(json['isSaved']), + memoryAt: serializer.fromJson(json['memoryAt']), + seenAt: serializer.fromJson(json['seenAt']), + showAt: serializer.fromJson(json['showAt']), + hideAt: serializer.fromJson(json['hideAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'deletedAt': serializer.toJson(deletedAt), + 'ownerId': serializer.toJson(ownerId), + 'type': serializer.toJson(type), + 'data': serializer.toJson(data), + 'isSaved': serializer.toJson(isSaved), + 'memoryAt': serializer.toJson(memoryAt), + 'seenAt': serializer.toJson(seenAt), + 'showAt': serializer.toJson(showAt), + 'hideAt': serializer.toJson(hideAt), + }; + } + + MemoryEntityData copyWith({ + String? id, + String? createdAt, + String? updatedAt, + Value deletedAt = const Value.absent(), + String? ownerId, + int? type, + String? data, + int? isSaved, + String? memoryAt, + Value seenAt = const Value.absent(), + Value showAt = const Value.absent(), + Value hideAt = const Value.absent(), + }) => MemoryEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt.present ? seenAt.value : this.seenAt, + showAt: showAt.present ? showAt.value : this.showAt, + hideAt: hideAt.present ? hideAt.value : this.hideAt, + ); + MemoryEntityData copyWithCompanion(MemoryEntityCompanion data) { + return MemoryEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + type: data.type.present ? data.type.value : this.type, + data: data.data.present ? data.data.value : this.data, + isSaved: data.isSaved.present ? data.isSaved.value : this.isSaved, + memoryAt: data.memoryAt.present ? data.memoryAt.value : this.memoryAt, + seenAt: data.seenAt.present ? data.seenAt.value : this.seenAt, + showAt: data.showAt.present ? data.showAt.value : this.showAt, + hideAt: data.hideAt.present ? data.hideAt.value : this.hideAt, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MemoryEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.deletedAt == this.deletedAt && + other.ownerId == this.ownerId && + other.type == this.type && + other.data == this.data && + other.isSaved == this.isSaved && + other.memoryAt == this.memoryAt && + other.seenAt == this.seenAt && + other.showAt == this.showAt && + other.hideAt == this.hideAt); +} + +class MemoryEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value deletedAt; + final Value ownerId; + final Value type; + final Value data; + final Value isSaved; + final Value memoryAt; + final Value seenAt; + final Value showAt; + final Value hideAt; + const MemoryEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.deletedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.type = const Value.absent(), + this.data = const Value.absent(), + this.isSaved = const Value.absent(), + this.memoryAt = const Value.absent(), + this.seenAt = const Value.absent(), + this.showAt = const Value.absent(), + this.hideAt = const Value.absent(), + }); + MemoryEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.deletedAt = const Value.absent(), + required String ownerId, + required int type, + required String data, + this.isSaved = const Value.absent(), + required String memoryAt, + this.seenAt = const Value.absent(), + this.showAt = const Value.absent(), + this.hideAt = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + type = Value(type), + data = Value(data), + memoryAt = Value(memoryAt); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? deletedAt, + Expression? ownerId, + Expression? type, + Expression? data, + Expression? isSaved, + Expression? memoryAt, + Expression? seenAt, + Expression? showAt, + Expression? hideAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (deletedAt != null) 'deleted_at': deletedAt, + if (ownerId != null) 'owner_id': ownerId, + if (type != null) 'type': type, + if (data != null) 'data': data, + if (isSaved != null) 'is_saved': isSaved, + if (memoryAt != null) 'memory_at': memoryAt, + if (seenAt != null) 'seen_at': seenAt, + if (showAt != null) 'show_at': showAt, + if (hideAt != null) 'hide_at': hideAt, + }); + } + + MemoryEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? deletedAt, + Value? ownerId, + Value? type, + Value? data, + Value? isSaved, + Value? memoryAt, + Value? seenAt, + Value? showAt, + Value? hideAt, + }) { + return MemoryEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt ?? this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt ?? this.seenAt, + showAt: showAt ?? this.showAt, + hideAt: hideAt ?? this.hideAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (data.present) { + map['data'] = Variable(data.value); + } + if (isSaved.present) { + map['is_saved'] = Variable(isSaved.value); + } + if (memoryAt.present) { + map['memory_at'] = Variable(memoryAt.value); + } + if (seenAt.present) { + map['seen_at'] = Variable(seenAt.value); + } + if (showAt.present) { + map['show_at'] = Variable(showAt.value); + } + if (hideAt.present) { + map['hide_at'] = Variable(hideAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } +} + +class MemoryAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MemoryAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES remote_asset_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn memoryId = GeneratedColumn( + 'memory_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES memory_entity(id)ON DELETE CASCADE', + ); + @override + List get $columns => [assetId, memoryId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_asset_entity'; + @override + Set get $primaryKey => {assetId, memoryId}; + @override + MemoryAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MemoryAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + memoryId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}memory_id'], + )!, + ); + } + + @override + MemoryAssetEntity createAlias(String alias) { + return MemoryAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const [ + 'PRIMARY KEY(asset_id, memory_id)', + ]; + @override + bool get dontWriteConstraints => true; +} + +class MemoryAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String memoryId; + const MemoryAssetEntityData({required this.assetId, required this.memoryId}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['memory_id'] = Variable(memoryId); + return map; + } + + factory MemoryAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MemoryAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + memoryId: serializer.fromJson(json['memoryId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'memoryId': serializer.toJson(memoryId), + }; + } + + MemoryAssetEntityData copyWith({String? assetId, String? memoryId}) => + MemoryAssetEntityData( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + MemoryAssetEntityData copyWithCompanion(MemoryAssetEntityCompanion data) { + return MemoryAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + memoryId: data.memoryId.present ? data.memoryId.value : this.memoryId, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, memoryId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MemoryAssetEntityData && + other.assetId == this.assetId && + other.memoryId == this.memoryId); +} + +class MemoryAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value memoryId; + const MemoryAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.memoryId = const Value.absent(), + }); + MemoryAssetEntityCompanion.insert({ + required String assetId, + required String memoryId, + }) : assetId = Value(assetId), + memoryId = Value(memoryId); + static Insertable custom({ + Expression? assetId, + Expression? memoryId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (memoryId != null) 'memory_id': memoryId, + }); + } + + MemoryAssetEntityCompanion copyWith({ + Value? assetId, + Value? memoryId, + }) { + return MemoryAssetEntityCompanion( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (memoryId.present) { + map['memory_id'] = Variable(memoryId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } +} + +class PersonEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PersonEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL REFERENCES user_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn faceAssetId = GeneratedColumn( + 'face_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL CHECK (is_favorite IN (0, 1))', + ); + late final GeneratedColumn isHidden = GeneratedColumn( + 'is_hidden', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL CHECK (is_hidden IN (0, 1))', + ); + late final GeneratedColumn color = GeneratedColumn( + 'color', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn birthDate = GeneratedColumn( + 'birth_date', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + isFavorite, + isHidden, + color, + birthDate, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'person_entity'; + @override + Set get $primaryKey => {id}; + @override + PersonEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PersonEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + faceAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}face_asset_id'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_favorite'], + )!, + isHidden: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_hidden'], + )!, + color: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}color'], + ), + birthDate: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}birth_date'], + ), + ); + } + + @override + PersonEntity createAlias(String alias) { + return PersonEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class PersonEntityData extends DataClass + implements Insertable { + final String id; + final String createdAt; + final String updatedAt; + final String ownerId; + final String name; + final String? faceAssetId; + final int isFavorite; + final int isHidden; + final String? color; + final String? birthDate; + const PersonEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.name, + this.faceAssetId, + required this.isFavorite, + required this.isHidden, + this.color, + this.birthDate, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + map['name'] = Variable(name); + if (!nullToAbsent || faceAssetId != null) { + map['face_asset_id'] = Variable(faceAssetId); + } + map['is_favorite'] = Variable(isFavorite); + map['is_hidden'] = Variable(isHidden); + if (!nullToAbsent || color != null) { + map['color'] = Variable(color); + } + if (!nullToAbsent || birthDate != null) { + map['birth_date'] = Variable(birthDate); + } + return map; + } + + factory PersonEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PersonEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + name: serializer.fromJson(json['name']), + faceAssetId: serializer.fromJson(json['faceAssetId']), + isFavorite: serializer.fromJson(json['isFavorite']), + isHidden: serializer.fromJson(json['isHidden']), + color: serializer.fromJson(json['color']), + birthDate: serializer.fromJson(json['birthDate']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'name': serializer.toJson(name), + 'faceAssetId': serializer.toJson(faceAssetId), + 'isFavorite': serializer.toJson(isFavorite), + 'isHidden': serializer.toJson(isHidden), + 'color': serializer.toJson(color), + 'birthDate': serializer.toJson(birthDate), + }; + } + + PersonEntityData copyWith({ + String? id, + String? createdAt, + String? updatedAt, + String? ownerId, + String? name, + Value faceAssetId = const Value.absent(), + int? isFavorite, + int? isHidden, + Value color = const Value.absent(), + Value birthDate = const Value.absent(), + }) => PersonEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId.present ? faceAssetId.value : this.faceAssetId, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color.present ? color.value : this.color, + birthDate: birthDate.present ? birthDate.value : this.birthDate, + ); + PersonEntityData copyWithCompanion(PersonEntityCompanion data) { + return PersonEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + name: data.name.present ? data.name.value : this.name, + faceAssetId: data.faceAssetId.present + ? data.faceAssetId.value + : this.faceAssetId, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + isHidden: data.isHidden.present ? data.isHidden.value : this.isHidden, + color: data.color.present ? data.color.value : this.color, + birthDate: data.birthDate.present ? data.birthDate.value : this.birthDate, + ); + } + + @override + String toString() { + return (StringBuffer('PersonEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + isFavorite, + isHidden, + color, + birthDate, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PersonEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.name == this.name && + other.faceAssetId == this.faceAssetId && + other.isFavorite == this.isFavorite && + other.isHidden == this.isHidden && + other.color == this.color && + other.birthDate == this.birthDate); +} + +class PersonEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value name; + final Value faceAssetId; + final Value isFavorite; + final Value isHidden; + final Value color; + final Value birthDate; + const PersonEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.name = const Value.absent(), + this.faceAssetId = const Value.absent(), + this.isFavorite = const Value.absent(), + this.isHidden = const Value.absent(), + this.color = const Value.absent(), + this.birthDate = const Value.absent(), + }); + PersonEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + required String name, + this.faceAssetId = const Value.absent(), + required int isFavorite, + required int isHidden, + this.color = const Value.absent(), + this.birthDate = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + name = Value(name), + isFavorite = Value(isFavorite), + isHidden = Value(isHidden); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? name, + Expression? faceAssetId, + Expression? isFavorite, + Expression? isHidden, + Expression? color, + Expression? birthDate, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (name != null) 'name': name, + if (faceAssetId != null) 'face_asset_id': faceAssetId, + if (isFavorite != null) 'is_favorite': isFavorite, + if (isHidden != null) 'is_hidden': isHidden, + if (color != null) 'color': color, + if (birthDate != null) 'birth_date': birthDate, + }); + } + + PersonEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? name, + Value? faceAssetId, + Value? isFavorite, + Value? isHidden, + Value? color, + Value? birthDate, + }) { + return PersonEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId ?? this.faceAssetId, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color ?? this.color, + birthDate: birthDate ?? this.birthDate, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (faceAssetId.present) { + map['face_asset_id'] = Variable(faceAssetId.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (isHidden.present) { + map['is_hidden'] = Variable(isHidden.value); + } + if (color.present) { + map['color'] = Variable(color.value); + } + if (birthDate.present) { + map['birth_date'] = Variable(birthDate.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PersonEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } +} + +class AssetFaceEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AssetFaceEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES remote_asset_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn personId = GeneratedColumn( + 'person_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL REFERENCES person_entity(id)ON DELETE SET NULL', + ); + late final GeneratedColumn imageWidth = GeneratedColumn( + 'image_width', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn imageHeight = GeneratedColumn( + 'image_height', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn boundingBoxX1 = GeneratedColumn( + 'bounding_box_x1', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn boundingBoxY1 = GeneratedColumn( + 'bounding_box_y1', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn boundingBoxX2 = GeneratedColumn( + 'bounding_box_x2', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn boundingBoxY2 = GeneratedColumn( + 'bounding_box_y2', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn sourceType = GeneratedColumn( + 'source_type', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn isVisible = GeneratedColumn( + 'is_visible', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 1 CHECK (is_visible IN (0, 1))', + defaultValue: const CustomExpression('1'), + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + @override + List get $columns => [ + id, + assetId, + personId, + imageWidth, + imageHeight, + boundingBoxX1, + boundingBoxY1, + boundingBoxX2, + boundingBoxY2, + sourceType, + isVisible, + deletedAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'asset_face_entity'; + @override + Set get $primaryKey => {id}; + @override + AssetFaceEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AssetFaceEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + personId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}person_id'], + ), + imageWidth: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}image_width'], + )!, + imageHeight: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}image_height'], + )!, + boundingBoxX1: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_x1'], + )!, + boundingBoxY1: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_y1'], + )!, + boundingBoxX2: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_x2'], + )!, + boundingBoxY2: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_y2'], + )!, + sourceType: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}source_type'], + )!, + isVisible: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_visible'], + )!, + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}deleted_at'], + ), + ); + } + + @override + AssetFaceEntity createAlias(String alias) { + return AssetFaceEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class AssetFaceEntityData extends DataClass + implements Insertable { + final String id; + final String assetId; + final String? personId; + final int imageWidth; + final int imageHeight; + final int boundingBoxX1; + final int boundingBoxY1; + final int boundingBoxX2; + final int boundingBoxY2; + final String sourceType; + final int isVisible; + final String? deletedAt; + const AssetFaceEntityData({ + required this.id, + required this.assetId, + this.personId, + required this.imageWidth, + required this.imageHeight, + required this.boundingBoxX1, + required this.boundingBoxY1, + required this.boundingBoxX2, + required this.boundingBoxY2, + required this.sourceType, + required this.isVisible, + this.deletedAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || personId != null) { + map['person_id'] = Variable(personId); + } + map['image_width'] = Variable(imageWidth); + map['image_height'] = Variable(imageHeight); + map['bounding_box_x1'] = Variable(boundingBoxX1); + map['bounding_box_y1'] = Variable(boundingBoxY1); + map['bounding_box_x2'] = Variable(boundingBoxX2); + map['bounding_box_y2'] = Variable(boundingBoxY2); + map['source_type'] = Variable(sourceType); + map['is_visible'] = Variable(isVisible); + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + return map; + } + + factory AssetFaceEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AssetFaceEntityData( + id: serializer.fromJson(json['id']), + assetId: serializer.fromJson(json['assetId']), + personId: serializer.fromJson(json['personId']), + imageWidth: serializer.fromJson(json['imageWidth']), + imageHeight: serializer.fromJson(json['imageHeight']), + boundingBoxX1: serializer.fromJson(json['boundingBoxX1']), + boundingBoxY1: serializer.fromJson(json['boundingBoxY1']), + boundingBoxX2: serializer.fromJson(json['boundingBoxX2']), + boundingBoxY2: serializer.fromJson(json['boundingBoxY2']), + sourceType: serializer.fromJson(json['sourceType']), + isVisible: serializer.fromJson(json['isVisible']), + deletedAt: serializer.fromJson(json['deletedAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'assetId': serializer.toJson(assetId), + 'personId': serializer.toJson(personId), + 'imageWidth': serializer.toJson(imageWidth), + 'imageHeight': serializer.toJson(imageHeight), + 'boundingBoxX1': serializer.toJson(boundingBoxX1), + 'boundingBoxY1': serializer.toJson(boundingBoxY1), + 'boundingBoxX2': serializer.toJson(boundingBoxX2), + 'boundingBoxY2': serializer.toJson(boundingBoxY2), + 'sourceType': serializer.toJson(sourceType), + 'isVisible': serializer.toJson(isVisible), + 'deletedAt': serializer.toJson(deletedAt), + }; + } + + AssetFaceEntityData copyWith({ + String? id, + String? assetId, + Value personId = const Value.absent(), + int? imageWidth, + int? imageHeight, + int? boundingBoxX1, + int? boundingBoxY1, + int? boundingBoxX2, + int? boundingBoxY2, + String? sourceType, + int? isVisible, + Value deletedAt = const Value.absent(), + }) => AssetFaceEntityData( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + personId: personId.present ? personId.value : this.personId, + imageWidth: imageWidth ?? this.imageWidth, + imageHeight: imageHeight ?? this.imageHeight, + boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1, + boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1, + boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2, + boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2, + sourceType: sourceType ?? this.sourceType, + isVisible: isVisible ?? this.isVisible, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + ); + AssetFaceEntityData copyWithCompanion(AssetFaceEntityCompanion data) { + return AssetFaceEntityData( + id: data.id.present ? data.id.value : this.id, + assetId: data.assetId.present ? data.assetId.value : this.assetId, + personId: data.personId.present ? data.personId.value : this.personId, + imageWidth: data.imageWidth.present + ? data.imageWidth.value + : this.imageWidth, + imageHeight: data.imageHeight.present + ? data.imageHeight.value + : this.imageHeight, + boundingBoxX1: data.boundingBoxX1.present + ? data.boundingBoxX1.value + : this.boundingBoxX1, + boundingBoxY1: data.boundingBoxY1.present + ? data.boundingBoxY1.value + : this.boundingBoxY1, + boundingBoxX2: data.boundingBoxX2.present + ? data.boundingBoxX2.value + : this.boundingBoxX2, + boundingBoxY2: data.boundingBoxY2.present + ? data.boundingBoxY2.value + : this.boundingBoxY2, + sourceType: data.sourceType.present + ? data.sourceType.value + : this.sourceType, + isVisible: data.isVisible.present ? data.isVisible.value : this.isVisible, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + ); + } + + @override + String toString() { + return (StringBuffer('AssetFaceEntityData(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('personId: $personId, ') + ..write('imageWidth: $imageWidth, ') + ..write('imageHeight: $imageHeight, ') + ..write('boundingBoxX1: $boundingBoxX1, ') + ..write('boundingBoxY1: $boundingBoxY1, ') + ..write('boundingBoxX2: $boundingBoxX2, ') + ..write('boundingBoxY2: $boundingBoxY2, ') + ..write('sourceType: $sourceType, ') + ..write('isVisible: $isVisible, ') + ..write('deletedAt: $deletedAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + assetId, + personId, + imageWidth, + imageHeight, + boundingBoxX1, + boundingBoxY1, + boundingBoxX2, + boundingBoxY2, + sourceType, + isVisible, + deletedAt, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AssetFaceEntityData && + other.id == this.id && + other.assetId == this.assetId && + other.personId == this.personId && + other.imageWidth == this.imageWidth && + other.imageHeight == this.imageHeight && + other.boundingBoxX1 == this.boundingBoxX1 && + other.boundingBoxY1 == this.boundingBoxY1 && + other.boundingBoxX2 == this.boundingBoxX2 && + other.boundingBoxY2 == this.boundingBoxY2 && + other.sourceType == this.sourceType && + other.isVisible == this.isVisible && + other.deletedAt == this.deletedAt); +} + +class AssetFaceEntityCompanion extends UpdateCompanion { + final Value id; + final Value assetId; + final Value personId; + final Value imageWidth; + final Value imageHeight; + final Value boundingBoxX1; + final Value boundingBoxY1; + final Value boundingBoxX2; + final Value boundingBoxY2; + final Value sourceType; + final Value isVisible; + final Value deletedAt; + const AssetFaceEntityCompanion({ + this.id = const Value.absent(), + this.assetId = const Value.absent(), + this.personId = const Value.absent(), + this.imageWidth = const Value.absent(), + this.imageHeight = const Value.absent(), + this.boundingBoxX1 = const Value.absent(), + this.boundingBoxY1 = const Value.absent(), + this.boundingBoxX2 = const Value.absent(), + this.boundingBoxY2 = const Value.absent(), + this.sourceType = const Value.absent(), + this.isVisible = const Value.absent(), + this.deletedAt = const Value.absent(), + }); + AssetFaceEntityCompanion.insert({ + required String id, + required String assetId, + this.personId = const Value.absent(), + required int imageWidth, + required int imageHeight, + required int boundingBoxX1, + required int boundingBoxY1, + required int boundingBoxX2, + required int boundingBoxY2, + required String sourceType, + this.isVisible = const Value.absent(), + this.deletedAt = const Value.absent(), + }) : id = Value(id), + assetId = Value(assetId), + imageWidth = Value(imageWidth), + imageHeight = Value(imageHeight), + boundingBoxX1 = Value(boundingBoxX1), + boundingBoxY1 = Value(boundingBoxY1), + boundingBoxX2 = Value(boundingBoxX2), + boundingBoxY2 = Value(boundingBoxY2), + sourceType = Value(sourceType); + static Insertable custom({ + Expression? id, + Expression? assetId, + Expression? personId, + Expression? imageWidth, + Expression? imageHeight, + Expression? boundingBoxX1, + Expression? boundingBoxY1, + Expression? boundingBoxX2, + Expression? boundingBoxY2, + Expression? sourceType, + Expression? isVisible, + Expression? deletedAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (assetId != null) 'asset_id': assetId, + if (personId != null) 'person_id': personId, + if (imageWidth != null) 'image_width': imageWidth, + if (imageHeight != null) 'image_height': imageHeight, + if (boundingBoxX1 != null) 'bounding_box_x1': boundingBoxX1, + if (boundingBoxY1 != null) 'bounding_box_y1': boundingBoxY1, + if (boundingBoxX2 != null) 'bounding_box_x2': boundingBoxX2, + if (boundingBoxY2 != null) 'bounding_box_y2': boundingBoxY2, + if (sourceType != null) 'source_type': sourceType, + if (isVisible != null) 'is_visible': isVisible, + if (deletedAt != null) 'deleted_at': deletedAt, + }); + } + + AssetFaceEntityCompanion copyWith({ + Value? id, + Value? assetId, + Value? personId, + Value? imageWidth, + Value? imageHeight, + Value? boundingBoxX1, + Value? boundingBoxY1, + Value? boundingBoxX2, + Value? boundingBoxY2, + Value? sourceType, + Value? isVisible, + Value? deletedAt, + }) { + return AssetFaceEntityCompanion( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + personId: personId ?? this.personId, + imageWidth: imageWidth ?? this.imageWidth, + imageHeight: imageHeight ?? this.imageHeight, + boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1, + boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1, + boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2, + boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2, + sourceType: sourceType ?? this.sourceType, + isVisible: isVisible ?? this.isVisible, + deletedAt: deletedAt ?? this.deletedAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (personId.present) { + map['person_id'] = Variable(personId.value); + } + if (imageWidth.present) { + map['image_width'] = Variable(imageWidth.value); + } + if (imageHeight.present) { + map['image_height'] = Variable(imageHeight.value); + } + if (boundingBoxX1.present) { + map['bounding_box_x1'] = Variable(boundingBoxX1.value); + } + if (boundingBoxY1.present) { + map['bounding_box_y1'] = Variable(boundingBoxY1.value); + } + if (boundingBoxX2.present) { + map['bounding_box_x2'] = Variable(boundingBoxX2.value); + } + if (boundingBoxY2.present) { + map['bounding_box_y2'] = Variable(boundingBoxY2.value); + } + if (sourceType.present) { + map['source_type'] = Variable(sourceType.value); + } + if (isVisible.present) { + map['is_visible'] = Variable(isVisible.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AssetFaceEntityCompanion(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('personId: $personId, ') + ..write('imageWidth: $imageWidth, ') + ..write('imageHeight: $imageHeight, ') + ..write('boundingBoxX1: $boundingBoxX1, ') + ..write('boundingBoxY1: $boundingBoxY1, ') + ..write('boundingBoxX2: $boundingBoxX2, ') + ..write('boundingBoxY2: $boundingBoxY2, ') + ..write('sourceType: $sourceType, ') + ..write('isVisible: $isVisible, ') + ..write('deletedAt: $deletedAt') + ..write(')')) + .toString(); + } +} + +class StoreEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + StoreEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn stringValue = GeneratedColumn( + 'string_value', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn intValue = GeneratedColumn( + 'int_value', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + @override + List get $columns => [id, stringValue, intValue]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'store_entity'; + @override + Set get $primaryKey => {id}; + @override + StoreEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return StoreEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}id'], + )!, + stringValue: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}string_value'], + ), + intValue: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}int_value'], + ), + ); + } + + @override + StoreEntity createAlias(String alias) { + return StoreEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class StoreEntityData extends DataClass implements Insertable { + final int id; + final String? stringValue; + final int? intValue; + const StoreEntityData({required this.id, this.stringValue, this.intValue}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + if (!nullToAbsent || stringValue != null) { + map['string_value'] = Variable(stringValue); + } + if (!nullToAbsent || intValue != null) { + map['int_value'] = Variable(intValue); + } + return map; + } + + factory StoreEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return StoreEntityData( + id: serializer.fromJson(json['id']), + stringValue: serializer.fromJson(json['stringValue']), + intValue: serializer.fromJson(json['intValue']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'stringValue': serializer.toJson(stringValue), + 'intValue': serializer.toJson(intValue), + }; + } + + StoreEntityData copyWith({ + int? id, + Value stringValue = const Value.absent(), + Value intValue = const Value.absent(), + }) => StoreEntityData( + id: id ?? this.id, + stringValue: stringValue.present ? stringValue.value : this.stringValue, + intValue: intValue.present ? intValue.value : this.intValue, + ); + StoreEntityData copyWithCompanion(StoreEntityCompanion data) { + return StoreEntityData( + id: data.id.present ? data.id.value : this.id, + stringValue: data.stringValue.present + ? data.stringValue.value + : this.stringValue, + intValue: data.intValue.present ? data.intValue.value : this.intValue, + ); + } + + @override + String toString() { + return (StringBuffer('StoreEntityData(') + ..write('id: $id, ') + ..write('stringValue: $stringValue, ') + ..write('intValue: $intValue') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, stringValue, intValue); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is StoreEntityData && + other.id == this.id && + other.stringValue == this.stringValue && + other.intValue == this.intValue); +} + +class StoreEntityCompanion extends UpdateCompanion { + final Value id; + final Value stringValue; + final Value intValue; + const StoreEntityCompanion({ + this.id = const Value.absent(), + this.stringValue = const Value.absent(), + this.intValue = const Value.absent(), + }); + StoreEntityCompanion.insert({ + required int id, + this.stringValue = const Value.absent(), + this.intValue = const Value.absent(), + }) : id = Value(id); + static Insertable custom({ + Expression? id, + Expression? stringValue, + Expression? intValue, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (stringValue != null) 'string_value': stringValue, + if (intValue != null) 'int_value': intValue, + }); + } + + StoreEntityCompanion copyWith({ + Value? id, + Value? stringValue, + Value? intValue, + }) { + return StoreEntityCompanion( + id: id ?? this.id, + stringValue: stringValue ?? this.stringValue, + intValue: intValue ?? this.intValue, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (stringValue.present) { + map['string_value'] = Variable(stringValue.value); + } + if (intValue.present) { + map['int_value'] = Variable(intValue.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('StoreEntityCompanion(') + ..write('id: $id, ') + ..write('stringValue: $stringValue, ') + ..write('intValue: $intValue') + ..write(')')) + .toString(); + } +} + +class TrashedLocalAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + TrashedLocalAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn durationMs = GeneratedColumn( + 'duration_ms', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0 CHECK (is_favorite IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn source = GeneratedColumn( + 'source', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn playbackStyle = GeneratedColumn( + 'playback_style', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0', + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationMs, + id, + albumId, + checksum, + isFavorite, + orientation, + source, + playbackStyle, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'trashed_local_asset_entity'; + @override + Set get $primaryKey => {id, albumId}; + @override + TrashedLocalAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return TrashedLocalAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationMs: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_ms'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_favorite'], + )!, + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}orientation'], + )!, + source: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}source'], + )!, + playbackStyle: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}playback_style'], + )!, + ); + } + + @override + TrashedLocalAssetEntity createAlias(String alias) { + return TrashedLocalAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id, album_id)']; + @override + bool get dontWriteConstraints => true; +} + +class TrashedLocalAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final String createdAt; + final String updatedAt; + final int? width; + final int? height; + final int? durationMs; + final String id; + final String albumId; + final String? checksum; + final int isFavorite; + final int orientation; + final int source; + final int playbackStyle; + const TrashedLocalAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationMs, + required this.id, + required this.albumId, + this.checksum, + required this.isFavorite, + required this.orientation, + required this.source, + required this.playbackStyle, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationMs != null) { + map['duration_ms'] = Variable(durationMs); + } + map['id'] = Variable(id); + map['album_id'] = Variable(albumId); + if (!nullToAbsent || checksum != null) { + map['checksum'] = Variable(checksum); + } + map['is_favorite'] = Variable(isFavorite); + map['orientation'] = Variable(orientation); + map['source'] = Variable(source); + map['playback_style'] = Variable(playbackStyle); + return map; + } + + factory TrashedLocalAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return TrashedLocalAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationMs: serializer.fromJson(json['durationMs']), + id: serializer.fromJson(json['id']), + albumId: serializer.fromJson(json['albumId']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + orientation: serializer.fromJson(json['orientation']), + source: serializer.fromJson(json['source']), + playbackStyle: serializer.fromJson(json['playbackStyle']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationMs': serializer.toJson(durationMs), + 'id': serializer.toJson(id), + 'albumId': serializer.toJson(albumId), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'orientation': serializer.toJson(orientation), + 'source': serializer.toJson(source), + 'playbackStyle': serializer.toJson(playbackStyle), + }; + } + + TrashedLocalAssetEntityData copyWith({ + String? name, + int? type, + String? createdAt, + String? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationMs = const Value.absent(), + String? id, + String? albumId, + Value checksum = const Value.absent(), + int? isFavorite, + int? orientation, + int? source, + int? playbackStyle, + }) => TrashedLocalAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationMs: durationMs.present ? durationMs.value : this.durationMs, + id: id ?? this.id, + albumId: albumId ?? this.albumId, + checksum: checksum.present ? checksum.value : this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + source: source ?? this.source, + playbackStyle: playbackStyle ?? this.playbackStyle, + ); + TrashedLocalAssetEntityData copyWithCompanion( + TrashedLocalAssetEntityCompanion data, + ) { + return TrashedLocalAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationMs: data.durationMs.present + ? data.durationMs.value + : this.durationMs, + id: data.id.present ? data.id.value : this.id, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + source: data.source.present ? data.source.value : this.source, + playbackStyle: data.playbackStyle.present + ? data.playbackStyle.value + : this.playbackStyle, + ); + } + + @override + String toString() { + return (StringBuffer('TrashedLocalAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationMs: $durationMs, ') + ..write('id: $id, ') + ..write('albumId: $albumId, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation, ') + ..write('source: $source, ') + ..write('playbackStyle: $playbackStyle') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationMs, + id, + albumId, + checksum, + isFavorite, + orientation, + source, + playbackStyle, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is TrashedLocalAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationMs == this.durationMs && + other.id == this.id && + other.albumId == this.albumId && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.orientation == this.orientation && + other.source == this.source && + other.playbackStyle == this.playbackStyle); +} + +class TrashedLocalAssetEntityCompanion + extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationMs; + final Value id; + final Value albumId; + final Value checksum; + final Value isFavorite; + final Value orientation; + final Value source; + final Value playbackStyle; + const TrashedLocalAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationMs = const Value.absent(), + this.id = const Value.absent(), + this.albumId = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + this.source = const Value.absent(), + this.playbackStyle = const Value.absent(), + }); + TrashedLocalAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationMs = const Value.absent(), + required String id, + required String albumId, + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + required int source, + this.playbackStyle = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id), + albumId = Value(albumId), + source = Value(source); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationMs, + Expression? id, + Expression? albumId, + Expression? checksum, + Expression? isFavorite, + Expression? orientation, + Expression? source, + Expression? playbackStyle, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationMs != null) 'duration_ms': durationMs, + if (id != null) 'id': id, + if (albumId != null) 'album_id': albumId, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (orientation != null) 'orientation': orientation, + if (source != null) 'source': source, + if (playbackStyle != null) 'playback_style': playbackStyle, + }); + } + + TrashedLocalAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationMs, + Value? id, + Value? albumId, + Value? checksum, + Value? isFavorite, + Value? orientation, + Value? source, + Value? playbackStyle, + }) { + return TrashedLocalAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationMs: durationMs ?? this.durationMs, + id: id ?? this.id, + albumId: albumId ?? this.albumId, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + source: source ?? this.source, + playbackStyle: playbackStyle ?? this.playbackStyle, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationMs.present) { + map['duration_ms'] = Variable(durationMs.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + if (source.present) { + map['source'] = Variable(source.value); + } + if (playbackStyle.present) { + map['playback_style'] = Variable(playbackStyle.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('TrashedLocalAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationMs: $durationMs, ') + ..write('id: $id, ') + ..write('albumId: $albumId, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation, ') + ..write('source: $source, ') + ..write('playbackStyle: $playbackStyle') + ..write(')')) + .toString(); + } +} + +class AssetEditEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AssetEditEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES remote_asset_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn action = GeneratedColumn( + 'action', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn parameters = + GeneratedColumn( + 'parameters', + aliasedName, + false, + type: DriftSqlType.blob, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn sequence = GeneratedColumn( + 'sequence', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + @override + List get $columns => [ + id, + assetId, + action, + parameters, + sequence, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'asset_edit_entity'; + @override + Set get $primaryKey => {id}; + @override + AssetEditEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AssetEditEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + action: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}action'], + )!, + parameters: attachedDatabase.typeMapping.read( + DriftSqlType.blob, + data['${effectivePrefix}parameters'], + )!, + sequence: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}sequence'], + )!, + ); + } + + @override + AssetEditEntity createAlias(String alias) { + return AssetEditEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class AssetEditEntityData extends DataClass + implements Insertable { + final String id; + final String assetId; + final int action; + final i2.Uint8List parameters; + final int sequence; + const AssetEditEntityData({ + required this.id, + required this.assetId, + required this.action, + required this.parameters, + required this.sequence, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['asset_id'] = Variable(assetId); + map['action'] = Variable(action); + map['parameters'] = Variable(parameters); + map['sequence'] = Variable(sequence); + return map; + } + + factory AssetEditEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AssetEditEntityData( + id: serializer.fromJson(json['id']), + assetId: serializer.fromJson(json['assetId']), + action: serializer.fromJson(json['action']), + parameters: serializer.fromJson(json['parameters']), + sequence: serializer.fromJson(json['sequence']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'assetId': serializer.toJson(assetId), + 'action': serializer.toJson(action), + 'parameters': serializer.toJson(parameters), + 'sequence': serializer.toJson(sequence), + }; + } + + AssetEditEntityData copyWith({ + String? id, + String? assetId, + int? action, + i2.Uint8List? parameters, + int? sequence, + }) => AssetEditEntityData( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + action: action ?? this.action, + parameters: parameters ?? this.parameters, + sequence: sequence ?? this.sequence, + ); + AssetEditEntityData copyWithCompanion(AssetEditEntityCompanion data) { + return AssetEditEntityData( + id: data.id.present ? data.id.value : this.id, + assetId: data.assetId.present ? data.assetId.value : this.assetId, + action: data.action.present ? data.action.value : this.action, + parameters: data.parameters.present + ? data.parameters.value + : this.parameters, + sequence: data.sequence.present ? data.sequence.value : this.sequence, + ); + } + + @override + String toString() { + return (StringBuffer('AssetEditEntityData(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('action: $action, ') + ..write('parameters: $parameters, ') + ..write('sequence: $sequence') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + assetId, + action, + $driftBlobEquality.hash(parameters), + sequence, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AssetEditEntityData && + other.id == this.id && + other.assetId == this.assetId && + other.action == this.action && + $driftBlobEquality.equals(other.parameters, this.parameters) && + other.sequence == this.sequence); +} + +class AssetEditEntityCompanion extends UpdateCompanion { + final Value id; + final Value assetId; + final Value action; + final Value parameters; + final Value sequence; + const AssetEditEntityCompanion({ + this.id = const Value.absent(), + this.assetId = const Value.absent(), + this.action = const Value.absent(), + this.parameters = const Value.absent(), + this.sequence = const Value.absent(), + }); + AssetEditEntityCompanion.insert({ + required String id, + required String assetId, + required int action, + required i2.Uint8List parameters, + required int sequence, + }) : id = Value(id), + assetId = Value(assetId), + action = Value(action), + parameters = Value(parameters), + sequence = Value(sequence); + static Insertable custom({ + Expression? id, + Expression? assetId, + Expression? action, + Expression? parameters, + Expression? sequence, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (assetId != null) 'asset_id': assetId, + if (action != null) 'action': action, + if (parameters != null) 'parameters': parameters, + if (sequence != null) 'sequence': sequence, + }); + } + + AssetEditEntityCompanion copyWith({ + Value? id, + Value? assetId, + Value? action, + Value? parameters, + Value? sequence, + }) { + return AssetEditEntityCompanion( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + action: action ?? this.action, + parameters: parameters ?? this.parameters, + sequence: sequence ?? this.sequence, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (action.present) { + map['action'] = Variable(action.value); + } + if (parameters.present) { + map['parameters'] = Variable(parameters.value); + } + if (sequence.present) { + map['sequence'] = Variable(sequence.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AssetEditEntityCompanion(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('action: $action, ') + ..write('parameters: $parameters, ') + ..write('sequence: $sequence') + ..write(')')) + .toString(); + } +} + +class Metadata extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + Metadata(this.attachedDatabase, [this._alias]); + late final GeneratedColumn key = GeneratedColumn( + 'key', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn value = GeneratedColumn( + 'value', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + @override + List get $columns => [key, value, updatedAt]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'metadata'; + @override + Set get $primaryKey => {key}; + @override + MetadataData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MetadataData( + key: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}key'], + )!, + value: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}value'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + ); + } + + @override + Metadata createAlias(String alias) { + return Metadata(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY("key")']; + @override + bool get dontWriteConstraints => true; +} + +class MetadataData extends DataClass implements Insertable { + final String key; + final String value; + final String updatedAt; + const MetadataData({ + required this.key, + required this.value, + required this.updatedAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['key'] = Variable(key); + map['value'] = Variable(value); + map['updated_at'] = Variable(updatedAt); + return map; + } + + factory MetadataData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MetadataData( + key: serializer.fromJson(json['key']), + value: serializer.fromJson(json['value']), + updatedAt: serializer.fromJson(json['updatedAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'key': serializer.toJson(key), + 'value': serializer.toJson(value), + 'updatedAt': serializer.toJson(updatedAt), + }; + } + + MetadataData copyWith({String? key, String? value, String? updatedAt}) => + MetadataData( + key: key ?? this.key, + value: value ?? this.value, + updatedAt: updatedAt ?? this.updatedAt, + ); + MetadataData copyWithCompanion(MetadataCompanion data) { + return MetadataData( + key: data.key.present ? data.key.value : this.key, + value: data.value.present ? data.value.value : this.value, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ); + } + + @override + String toString() { + return (StringBuffer('MetadataData(') + ..write('key: $key, ') + ..write('value: $value, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(key, value, updatedAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MetadataData && + other.key == this.key && + other.value == this.value && + other.updatedAt == this.updatedAt); +} + +class MetadataCompanion extends UpdateCompanion { + final Value key; + final Value value; + final Value updatedAt; + const MetadataCompanion({ + this.key = const Value.absent(), + this.value = const Value.absent(), + this.updatedAt = const Value.absent(), + }); + MetadataCompanion.insert({ + required String key, + required String value, + this.updatedAt = const Value.absent(), + }) : key = Value(key), + value = Value(value); + static Insertable custom({ + Expression? key, + Expression? value, + Expression? updatedAt, + }) { + return RawValuesInsertable({ + if (key != null) 'key': key, + if (value != null) 'value': value, + if (updatedAt != null) 'updated_at': updatedAt, + }); + } + + MetadataCompanion copyWith({ + Value? key, + Value? value, + Value? updatedAt, + }) { + return MetadataCompanion( + key: key ?? this.key, + value: value ?? this.value, + updatedAt: updatedAt ?? this.updatedAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (key.present) { + map['key'] = Variable(key.value); + } + if (value.present) { + map['value'] = Variable(value.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MetadataCompanion(') + ..write('key: $key, ') + ..write('value: $value, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } +} + +class DatabaseAtV26 extends GeneratedDatabase { + DatabaseAtV26(QueryExecutor e) : super(e); + late final UserEntity userEntity = UserEntity(this); + late final RemoteAssetEntity remoteAssetEntity = RemoteAssetEntity(this); + late final StackEntity stackEntity = StackEntity(this); + late final LocalAssetEntity localAssetEntity = LocalAssetEntity(this); + late final RemoteAlbumEntity remoteAlbumEntity = RemoteAlbumEntity(this); + late final LocalAlbumEntity localAlbumEntity = LocalAlbumEntity(this); + late final LocalAlbumAssetEntity localAlbumAssetEntity = + LocalAlbumAssetEntity(this); + late final Index idxLocalAlbumAssetAlbumAsset = Index( + 'idx_local_album_asset_album_asset', + 'CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)', + ); + late final Index idxLocalAssetChecksum = Index( + 'idx_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)', + ); + late final Index idxLocalAssetCloudId = Index( + 'idx_local_asset_cloud_id', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)', + ); + late final Index idxStackPrimaryAssetId = Index( + 'idx_stack_primary_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)', + ); + late final Index uQRemoteAssetsOwnerChecksum = Index( + 'UQ_remote_assets_owner_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)', + ); + late final Index uQRemoteAssetsOwnerLibraryChecksum = Index( + 'UQ_remote_assets_owner_library_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)', + ); + late final Index idxRemoteAssetChecksum = Index( + 'idx_remote_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)', + ); + late final Index idxRemoteAssetStackId = Index( + 'idx_remote_asset_stack_id', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)', + ); + late final Index idxRemoteAssetOwnerVisibilityDeletedCreated = Index( + 'idx_remote_asset_owner_visibility_deleted_created', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_visibility_deleted_created ON remote_asset_entity (owner_id, visibility, deleted_at, created_at DESC)', + ); + late final AuthUserEntity authUserEntity = AuthUserEntity(this); + late final UserMetadataEntity userMetadataEntity = UserMetadataEntity(this); + late final PartnerEntity partnerEntity = PartnerEntity(this); + late final RemoteExifEntity remoteExifEntity = RemoteExifEntity(this); + late final RemoteAlbumAssetEntity remoteAlbumAssetEntity = + RemoteAlbumAssetEntity(this); + late final RemoteAlbumUserEntity remoteAlbumUserEntity = + RemoteAlbumUserEntity(this); + late final RemoteAssetCloudIdEntity remoteAssetCloudIdEntity = + RemoteAssetCloudIdEntity(this); + late final MemoryEntity memoryEntity = MemoryEntity(this); + late final MemoryAssetEntity memoryAssetEntity = MemoryAssetEntity(this); + late final PersonEntity personEntity = PersonEntity(this); + late final AssetFaceEntity assetFaceEntity = AssetFaceEntity(this); + late final StoreEntity storeEntity = StoreEntity(this); + late final TrashedLocalAssetEntity trashedLocalAssetEntity = + TrashedLocalAssetEntity(this); + late final AssetEditEntity assetEditEntity = AssetEditEntity(this); + late final Metadata metadata = Metadata(this); + late final Index idxPartnerSharedWithId = Index( + 'idx_partner_shared_with_id', + 'CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)', + ); + late final Index idxLatLng = Index( + 'idx_lat_lng', + 'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)', + ); + late final Index idxRemoteExifCity = Index( + 'idx_remote_exif_city', + 'CREATE INDEX IF NOT EXISTS idx_remote_exif_city ON remote_exif_entity (city) WHERE city IS NOT NULL', + ); + late final Index idxRemoteAlbumAssetAlbumAsset = Index( + 'idx_remote_album_asset_album_asset', + 'CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)', + ); + late final Index idxRemoteAssetCloudId = Index( + 'idx_remote_asset_cloud_id', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)', + ); + late final Index idxPersonOwnerId = Index( + 'idx_person_owner_id', + 'CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)', + ); + late final Index idxAssetFacePersonId = Index( + 'idx_asset_face_person_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)', + ); + late final Index idxAssetFaceAssetId = Index( + 'idx_asset_face_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)', + ); + late final Index idxAssetFaceVisiblePerson = Index( + 'idx_asset_face_visible_person', + 'CREATE INDEX IF NOT EXISTS idx_asset_face_visible_person ON asset_face_entity (person_id, asset_id) WHERE is_visible = 1 AND deleted_at IS NULL', + ); + late final Index idxTrashedLocalAssetChecksum = Index( + 'idx_trashed_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)', + ); + late final Index idxTrashedLocalAssetAlbum = Index( + 'idx_trashed_local_asset_album', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)', + ); + late final Index idxAssetEditAssetId = Index( + 'idx_asset_edit_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_edit_asset_id ON asset_edit_entity (asset_id)', + ); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [ + userEntity, + remoteAssetEntity, + stackEntity, + localAssetEntity, + remoteAlbumEntity, + localAlbumEntity, + localAlbumAssetEntity, + idxLocalAlbumAssetAlbumAsset, + idxLocalAssetChecksum, + idxLocalAssetCloudId, + idxStackPrimaryAssetId, + uQRemoteAssetsOwnerChecksum, + uQRemoteAssetsOwnerLibraryChecksum, + idxRemoteAssetChecksum, + idxRemoteAssetStackId, + idxRemoteAssetOwnerVisibilityDeletedCreated, + authUserEntity, + userMetadataEntity, + partnerEntity, + remoteExifEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + remoteAssetCloudIdEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + assetFaceEntity, + storeEntity, + trashedLocalAssetEntity, + assetEditEntity, + metadata, + idxPartnerSharedWithId, + idxLatLng, + idxRemoteExifCity, + idxRemoteAlbumAssetAlbumAsset, + idxRemoteAssetCloudId, + idxPersonOwnerId, + idxAssetFacePersonId, + idxAssetFaceAssetId, + idxAssetFaceVisiblePerson, + idxTrashedLocalAssetChecksum, + idxTrashedLocalAssetAlbum, + idxAssetEditAssetId, + ]; + @override + StreamQueryUpdateRules get streamUpdateRules => const StreamQueryUpdateRules([ + WritePropagation( + on: TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('remote_asset_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('stack_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_asset_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('remote_album_entity', kind: UpdateKind.update)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_album_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('local_album_entity', kind: UpdateKind.update)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'local_asset_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [ + TableUpdate('local_album_asset_entity', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'local_album_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [ + TableUpdate('local_album_asset_entity', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('user_metadata_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('partner_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('partner_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_asset_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('remote_exif_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_asset_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [ + TableUpdate('remote_album_asset_entity', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_album_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [ + TableUpdate('remote_album_asset_entity', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_album_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [ + TableUpdate('remote_album_user_entity', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [ + TableUpdate('remote_album_user_entity', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_asset_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [ + TableUpdate('remote_asset_cloud_id_entity', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('memory_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_asset_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('memory_asset_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'memory_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('memory_asset_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('person_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_asset_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('asset_face_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'person_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('asset_face_entity', kind: UpdateKind.update)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_asset_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('asset_edit_entity', kind: UpdateKind.delete)], + ), + ]); + @override + int get schemaVersion => 26; + @override + DriftDatabaseOptions get options => + const DriftDatabaseOptions(storeDateTimeAsText: true); +} diff --git a/mobile/test/drift/main/generated/schema_v27.dart b/mobile/test/drift/main/generated/schema_v27.dart new file mode 100644 index 0000000000..2b02946175 --- /dev/null +++ b/mobile/test/drift/main/generated/schema_v27.dart @@ -0,0 +1,9384 @@ +// dart format width=80 +import 'dart:typed_data' as i2; +// GENERATED BY drift_dev, DO NOT MODIFY. +// ignore_for_file: type=lint,unused_import +// +import 'package:drift/drift.dart'; + +class UserEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn email = GeneratedColumn( + 'email', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn hasProfileImage = GeneratedColumn( + 'has_profile_image', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: + 'NOT NULL DEFAULT 0 CHECK (has_profile_image IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn profileChangedAt = GeneratedColumn( + 'profile_changed_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn avatarColor = GeneratedColumn( + 'avatar_color', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0', + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + id, + name, + email, + hasProfileImage, + profileChangedAt, + avatarColor, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_entity'; + @override + Set get $primaryKey => {id}; + @override + UserEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + email: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}email'], + )!, + hasProfileImage: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}has_profile_image'], + )!, + profileChangedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}profile_changed_at'], + )!, + avatarColor: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}avatar_color'], + )!, + ); + } + + @override + UserEntity createAlias(String alias) { + return UserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class UserEntityData extends DataClass implements Insertable { + final String id; + final String name; + final String email; + final int hasProfileImage; + final String profileChangedAt; + final int avatarColor; + const UserEntityData({ + required this.id, + required this.name, + required this.email, + required this.hasProfileImage, + required this.profileChangedAt, + required this.avatarColor, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['email'] = Variable(email); + map['has_profile_image'] = Variable(hasProfileImage); + map['profile_changed_at'] = Variable(profileChangedAt); + map['avatar_color'] = Variable(avatarColor); + return map; + } + + factory UserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + email: serializer.fromJson(json['email']), + hasProfileImage: serializer.fromJson(json['hasProfileImage']), + profileChangedAt: serializer.fromJson(json['profileChangedAt']), + avatarColor: serializer.fromJson(json['avatarColor']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'email': serializer.toJson(email), + 'hasProfileImage': serializer.toJson(hasProfileImage), + 'profileChangedAt': serializer.toJson(profileChangedAt), + 'avatarColor': serializer.toJson(avatarColor), + }; + } + + UserEntityData copyWith({ + String? id, + String? name, + String? email, + int? hasProfileImage, + String? profileChangedAt, + int? avatarColor, + }) => UserEntityData( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + ); + UserEntityData copyWithCompanion(UserEntityCompanion data) { + return UserEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + email: data.email.present ? data.email.value : this.email, + hasProfileImage: data.hasProfileImage.present + ? data.hasProfileImage.value + : this.hasProfileImage, + profileChangedAt: data.profileChangedAt.present + ? data.profileChangedAt.value + : this.profileChangedAt, + avatarColor: data.avatarColor.present + ? data.avatarColor.value + : this.avatarColor, + ); + } + + @override + String toString() { + return (StringBuffer('UserEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + email, + hasProfileImage, + profileChangedAt, + avatarColor, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserEntityData && + other.id == this.id && + other.name == this.name && + other.email == this.email && + other.hasProfileImage == this.hasProfileImage && + other.profileChangedAt == this.profileChangedAt && + other.avatarColor == this.avatarColor); +} + +class UserEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value email; + final Value hasProfileImage; + final Value profileChangedAt; + final Value avatarColor; + const UserEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.email = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + }); + UserEntityCompanion.insert({ + required String id, + required String name, + required String email, + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + }) : id = Value(id), + name = Value(name), + email = Value(email); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? email, + Expression? hasProfileImage, + Expression? profileChangedAt, + Expression? avatarColor, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (email != null) 'email': email, + if (hasProfileImage != null) 'has_profile_image': hasProfileImage, + if (profileChangedAt != null) 'profile_changed_at': profileChangedAt, + if (avatarColor != null) 'avatar_color': avatarColor, + }); + } + + UserEntityCompanion copyWith({ + Value? id, + Value? name, + Value? email, + Value? hasProfileImage, + Value? profileChangedAt, + Value? avatarColor, + }) { + return UserEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (email.present) { + map['email'] = Variable(email.value); + } + if (hasProfileImage.present) { + map['has_profile_image'] = Variable(hasProfileImage.value); + } + if (profileChangedAt.present) { + map['profile_changed_at'] = Variable(profileChangedAt.value); + } + if (avatarColor.present) { + map['avatar_color'] = Variable(avatarColor.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor') + ..write(')')) + .toString(); + } +} + +class RemoteAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn durationMs = GeneratedColumn( + 'duration_ms', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0 CHECK (is_favorite IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL REFERENCES user_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn localDateTime = GeneratedColumn( + 'local_date_time', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn thumbHash = GeneratedColumn( + 'thumb_hash', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn uploadedAt = GeneratedColumn( + 'uploaded_at', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn livePhotoVideoId = GeneratedColumn( + 'live_photo_video_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn visibility = GeneratedColumn( + 'visibility', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn stackId = GeneratedColumn( + 'stack_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn libraryId = GeneratedColumn( + 'library_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn isEdited = GeneratedColumn( + 'is_edited', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0 CHECK (is_edited IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationMs, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + uploadedAt, + livePhotoVideoId, + visibility, + stackId, + libraryId, + isEdited, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_asset_entity'; + @override + Set get $primaryKey => {id}; + @override + RemoteAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationMs: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_ms'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + )!, + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_favorite'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + localDateTime: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}local_date_time'], + ), + thumbHash: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumb_hash'], + ), + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}deleted_at'], + ), + uploadedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}uploaded_at'], + ), + livePhotoVideoId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}live_photo_video_id'], + ), + visibility: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}visibility'], + )!, + stackId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}stack_id'], + ), + libraryId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}library_id'], + ), + isEdited: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_edited'], + )!, + ); + } + + @override + RemoteAssetEntity createAlias(String alias) { + return RemoteAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class RemoteAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final String createdAt; + final String updatedAt; + final int? width; + final int? height; + final int? durationMs; + final String id; + final String checksum; + final int isFavorite; + final String ownerId; + final String? localDateTime; + final String? thumbHash; + final String? deletedAt; + final String? uploadedAt; + final String? livePhotoVideoId; + final int visibility; + final String? stackId; + final String? libraryId; + final int isEdited; + const RemoteAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationMs, + required this.id, + required this.checksum, + required this.isFavorite, + required this.ownerId, + this.localDateTime, + this.thumbHash, + this.deletedAt, + this.uploadedAt, + this.livePhotoVideoId, + required this.visibility, + this.stackId, + this.libraryId, + required this.isEdited, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationMs != null) { + map['duration_ms'] = Variable(durationMs); + } + map['id'] = Variable(id); + map['checksum'] = Variable(checksum); + map['is_favorite'] = Variable(isFavorite); + map['owner_id'] = Variable(ownerId); + if (!nullToAbsent || localDateTime != null) { + map['local_date_time'] = Variable(localDateTime); + } + if (!nullToAbsent || thumbHash != null) { + map['thumb_hash'] = Variable(thumbHash); + } + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + if (!nullToAbsent || uploadedAt != null) { + map['uploaded_at'] = Variable(uploadedAt); + } + if (!nullToAbsent || livePhotoVideoId != null) { + map['live_photo_video_id'] = Variable(livePhotoVideoId); + } + map['visibility'] = Variable(visibility); + if (!nullToAbsent || stackId != null) { + map['stack_id'] = Variable(stackId); + } + if (!nullToAbsent || libraryId != null) { + map['library_id'] = Variable(libraryId); + } + map['is_edited'] = Variable(isEdited); + return map; + } + + factory RemoteAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationMs: serializer.fromJson(json['durationMs']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + ownerId: serializer.fromJson(json['ownerId']), + localDateTime: serializer.fromJson(json['localDateTime']), + thumbHash: serializer.fromJson(json['thumbHash']), + deletedAt: serializer.fromJson(json['deletedAt']), + uploadedAt: serializer.fromJson(json['uploadedAt']), + livePhotoVideoId: serializer.fromJson(json['livePhotoVideoId']), + visibility: serializer.fromJson(json['visibility']), + stackId: serializer.fromJson(json['stackId']), + libraryId: serializer.fromJson(json['libraryId']), + isEdited: serializer.fromJson(json['isEdited']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationMs': serializer.toJson(durationMs), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'ownerId': serializer.toJson(ownerId), + 'localDateTime': serializer.toJson(localDateTime), + 'thumbHash': serializer.toJson(thumbHash), + 'deletedAt': serializer.toJson(deletedAt), + 'uploadedAt': serializer.toJson(uploadedAt), + 'livePhotoVideoId': serializer.toJson(livePhotoVideoId), + 'visibility': serializer.toJson(visibility), + 'stackId': serializer.toJson(stackId), + 'libraryId': serializer.toJson(libraryId), + 'isEdited': serializer.toJson(isEdited), + }; + } + + RemoteAssetEntityData copyWith({ + String? name, + int? type, + String? createdAt, + String? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationMs = const Value.absent(), + String? id, + String? checksum, + int? isFavorite, + String? ownerId, + Value localDateTime = const Value.absent(), + Value thumbHash = const Value.absent(), + Value deletedAt = const Value.absent(), + Value uploadedAt = const Value.absent(), + Value livePhotoVideoId = const Value.absent(), + int? visibility, + Value stackId = const Value.absent(), + Value libraryId = const Value.absent(), + int? isEdited, + }) => RemoteAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationMs: durationMs.present ? durationMs.value : this.durationMs, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime.present + ? localDateTime.value + : this.localDateTime, + thumbHash: thumbHash.present ? thumbHash.value : this.thumbHash, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + uploadedAt: uploadedAt.present ? uploadedAt.value : this.uploadedAt, + livePhotoVideoId: livePhotoVideoId.present + ? livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId.present ? stackId.value : this.stackId, + libraryId: libraryId.present ? libraryId.value : this.libraryId, + isEdited: isEdited ?? this.isEdited, + ); + RemoteAssetEntityData copyWithCompanion(RemoteAssetEntityCompanion data) { + return RemoteAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationMs: data.durationMs.present + ? data.durationMs.value + : this.durationMs, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + localDateTime: data.localDateTime.present + ? data.localDateTime.value + : this.localDateTime, + thumbHash: data.thumbHash.present ? data.thumbHash.value : this.thumbHash, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + uploadedAt: data.uploadedAt.present + ? data.uploadedAt.value + : this.uploadedAt, + livePhotoVideoId: data.livePhotoVideoId.present + ? data.livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: data.visibility.present + ? data.visibility.value + : this.visibility, + stackId: data.stackId.present ? data.stackId.value : this.stackId, + libraryId: data.libraryId.present ? data.libraryId.value : this.libraryId, + isEdited: data.isEdited.present ? data.isEdited.value : this.isEdited, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationMs: $durationMs, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('uploadedAt: $uploadedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId, ') + ..write('libraryId: $libraryId, ') + ..write('isEdited: $isEdited') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationMs, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + uploadedAt, + livePhotoVideoId, + visibility, + stackId, + libraryId, + isEdited, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationMs == this.durationMs && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.ownerId == this.ownerId && + other.localDateTime == this.localDateTime && + other.thumbHash == this.thumbHash && + other.deletedAt == this.deletedAt && + other.uploadedAt == this.uploadedAt && + other.livePhotoVideoId == this.livePhotoVideoId && + other.visibility == this.visibility && + other.stackId == this.stackId && + other.libraryId == this.libraryId && + other.isEdited == this.isEdited); +} + +class RemoteAssetEntityCompanion + extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationMs; + final Value id; + final Value checksum; + final Value isFavorite; + final Value ownerId; + final Value localDateTime; + final Value thumbHash; + final Value deletedAt; + final Value uploadedAt; + final Value livePhotoVideoId; + final Value visibility; + final Value stackId; + final Value libraryId; + final Value isEdited; + const RemoteAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationMs = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.ownerId = const Value.absent(), + this.localDateTime = const Value.absent(), + this.thumbHash = const Value.absent(), + this.deletedAt = const Value.absent(), + this.uploadedAt = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + this.visibility = const Value.absent(), + this.stackId = const Value.absent(), + this.libraryId = const Value.absent(), + this.isEdited = const Value.absent(), + }); + RemoteAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationMs = const Value.absent(), + required String id, + required String checksum, + this.isFavorite = const Value.absent(), + required String ownerId, + this.localDateTime = const Value.absent(), + this.thumbHash = const Value.absent(), + this.deletedAt = const Value.absent(), + this.uploadedAt = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + required int visibility, + this.stackId = const Value.absent(), + this.libraryId = const Value.absent(), + this.isEdited = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id), + checksum = Value(checksum), + ownerId = Value(ownerId), + visibility = Value(visibility); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationMs, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? ownerId, + Expression? localDateTime, + Expression? thumbHash, + Expression? deletedAt, + Expression? uploadedAt, + Expression? livePhotoVideoId, + Expression? visibility, + Expression? stackId, + Expression? libraryId, + Expression? isEdited, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationMs != null) 'duration_ms': durationMs, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (ownerId != null) 'owner_id': ownerId, + if (localDateTime != null) 'local_date_time': localDateTime, + if (thumbHash != null) 'thumb_hash': thumbHash, + if (deletedAt != null) 'deleted_at': deletedAt, + if (uploadedAt != null) 'uploaded_at': uploadedAt, + if (livePhotoVideoId != null) 'live_photo_video_id': livePhotoVideoId, + if (visibility != null) 'visibility': visibility, + if (stackId != null) 'stack_id': stackId, + if (libraryId != null) 'library_id': libraryId, + if (isEdited != null) 'is_edited': isEdited, + }); + } + + RemoteAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationMs, + Value? id, + Value? checksum, + Value? isFavorite, + Value? ownerId, + Value? localDateTime, + Value? thumbHash, + Value? deletedAt, + Value? uploadedAt, + Value? livePhotoVideoId, + Value? visibility, + Value? stackId, + Value? libraryId, + Value? isEdited, + }) { + return RemoteAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationMs: durationMs ?? this.durationMs, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime ?? this.localDateTime, + thumbHash: thumbHash ?? this.thumbHash, + deletedAt: deletedAt ?? this.deletedAt, + uploadedAt: uploadedAt ?? this.uploadedAt, + livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId ?? this.stackId, + libraryId: libraryId ?? this.libraryId, + isEdited: isEdited ?? this.isEdited, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationMs.present) { + map['duration_ms'] = Variable(durationMs.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (localDateTime.present) { + map['local_date_time'] = Variable(localDateTime.value); + } + if (thumbHash.present) { + map['thumb_hash'] = Variable(thumbHash.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + if (uploadedAt.present) { + map['uploaded_at'] = Variable(uploadedAt.value); + } + if (livePhotoVideoId.present) { + map['live_photo_video_id'] = Variable(livePhotoVideoId.value); + } + if (visibility.present) { + map['visibility'] = Variable(visibility.value); + } + if (stackId.present) { + map['stack_id'] = Variable(stackId.value); + } + if (libraryId.present) { + map['library_id'] = Variable(libraryId.value); + } + if (isEdited.present) { + map['is_edited'] = Variable(isEdited.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationMs: $durationMs, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('uploadedAt: $uploadedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId, ') + ..write('libraryId: $libraryId, ') + ..write('isEdited: $isEdited') + ..write(')')) + .toString(); + } +} + +class StackEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + StackEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL REFERENCES user_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn primaryAssetId = GeneratedColumn( + 'primary_asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + primaryAssetId, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'stack_entity'; + @override + Set get $primaryKey => {id}; + @override + StackEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return StackEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + primaryAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}primary_asset_id'], + )!, + ); + } + + @override + StackEntity createAlias(String alias) { + return StackEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class StackEntityData extends DataClass implements Insertable { + final String id; + final String createdAt; + final String updatedAt; + final String ownerId; + final String primaryAssetId; + const StackEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.primaryAssetId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + map['primary_asset_id'] = Variable(primaryAssetId); + return map; + } + + factory StackEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return StackEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + primaryAssetId: serializer.fromJson(json['primaryAssetId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'primaryAssetId': serializer.toJson(primaryAssetId), + }; + } + + StackEntityData copyWith({ + String? id, + String? createdAt, + String? updatedAt, + String? ownerId, + String? primaryAssetId, + }) => StackEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + StackEntityData copyWithCompanion(StackEntityCompanion data) { + return StackEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + primaryAssetId: data.primaryAssetId.present + ? data.primaryAssetId.value + : this.primaryAssetId, + ); + } + + @override + String toString() { + return (StringBuffer('StackEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(id, createdAt, updatedAt, ownerId, primaryAssetId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is StackEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.primaryAssetId == this.primaryAssetId); +} + +class StackEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value primaryAssetId; + const StackEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.primaryAssetId = const Value.absent(), + }); + StackEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + required String primaryAssetId, + }) : id = Value(id), + ownerId = Value(ownerId), + primaryAssetId = Value(primaryAssetId); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? primaryAssetId, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (primaryAssetId != null) 'primary_asset_id': primaryAssetId, + }); + } + + StackEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? primaryAssetId, + }) { + return StackEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (primaryAssetId.present) { + map['primary_asset_id'] = Variable(primaryAssetId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('StackEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } +} + +class LocalAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn durationMs = GeneratedColumn( + 'duration_ms', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0 CHECK (is_favorite IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn iCloudId = GeneratedColumn( + 'i_cloud_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn adjustmentTime = GeneratedColumn( + 'adjustment_time', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn playbackStyle = GeneratedColumn( + 'playback_style', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0', + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationMs, + id, + checksum, + isFavorite, + orientation, + iCloudId, + adjustmentTime, + latitude, + longitude, + playbackStyle, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_asset_entity'; + @override + Set get $primaryKey => {id}; + @override + LocalAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationMs: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_ms'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_favorite'], + )!, + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}orientation'], + )!, + iCloudId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}i_cloud_id'], + ), + adjustmentTime: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}adjustment_time'], + ), + latitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + playbackStyle: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}playback_style'], + )!, + ); + } + + @override + LocalAssetEntity createAlias(String alias) { + return LocalAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class LocalAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final String createdAt; + final String updatedAt; + final int? width; + final int? height; + final int? durationMs; + final String id; + final String? checksum; + final int isFavorite; + final int orientation; + final String? iCloudId; + final String? adjustmentTime; + final double? latitude; + final double? longitude; + final int playbackStyle; + const LocalAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationMs, + required this.id, + this.checksum, + required this.isFavorite, + required this.orientation, + this.iCloudId, + this.adjustmentTime, + this.latitude, + this.longitude, + required this.playbackStyle, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationMs != null) { + map['duration_ms'] = Variable(durationMs); + } + map['id'] = Variable(id); + if (!nullToAbsent || checksum != null) { + map['checksum'] = Variable(checksum); + } + map['is_favorite'] = Variable(isFavorite); + map['orientation'] = Variable(orientation); + if (!nullToAbsent || iCloudId != null) { + map['i_cloud_id'] = Variable(iCloudId); + } + if (!nullToAbsent || adjustmentTime != null) { + map['adjustment_time'] = Variable(adjustmentTime); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = Variable(longitude); + } + map['playback_style'] = Variable(playbackStyle); + return map; + } + + factory LocalAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationMs: serializer.fromJson(json['durationMs']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + orientation: serializer.fromJson(json['orientation']), + iCloudId: serializer.fromJson(json['iCloudId']), + adjustmentTime: serializer.fromJson(json['adjustmentTime']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + playbackStyle: serializer.fromJson(json['playbackStyle']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationMs': serializer.toJson(durationMs), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'orientation': serializer.toJson(orientation), + 'iCloudId': serializer.toJson(iCloudId), + 'adjustmentTime': serializer.toJson(adjustmentTime), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + 'playbackStyle': serializer.toJson(playbackStyle), + }; + } + + LocalAssetEntityData copyWith({ + String? name, + int? type, + String? createdAt, + String? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationMs = const Value.absent(), + String? id, + Value checksum = const Value.absent(), + int? isFavorite, + int? orientation, + Value iCloudId = const Value.absent(), + Value adjustmentTime = const Value.absent(), + Value latitude = const Value.absent(), + Value longitude = const Value.absent(), + int? playbackStyle, + }) => LocalAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationMs: durationMs.present ? durationMs.value : this.durationMs, + id: id ?? this.id, + checksum: checksum.present ? checksum.value : this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + iCloudId: iCloudId.present ? iCloudId.value : this.iCloudId, + adjustmentTime: adjustmentTime.present + ? adjustmentTime.value + : this.adjustmentTime, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + playbackStyle: playbackStyle ?? this.playbackStyle, + ); + LocalAssetEntityData copyWithCompanion(LocalAssetEntityCompanion data) { + return LocalAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationMs: data.durationMs.present + ? data.durationMs.value + : this.durationMs, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + iCloudId: data.iCloudId.present ? data.iCloudId.value : this.iCloudId, + adjustmentTime: data.adjustmentTime.present + ? data.adjustmentTime.value + : this.adjustmentTime, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + playbackStyle: data.playbackStyle.present + ? data.playbackStyle.value + : this.playbackStyle, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationMs: $durationMs, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation, ') + ..write('iCloudId: $iCloudId, ') + ..write('adjustmentTime: $adjustmentTime, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('playbackStyle: $playbackStyle') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationMs, + id, + checksum, + isFavorite, + orientation, + iCloudId, + adjustmentTime, + latitude, + longitude, + playbackStyle, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationMs == this.durationMs && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.orientation == this.orientation && + other.iCloudId == this.iCloudId && + other.adjustmentTime == this.adjustmentTime && + other.latitude == this.latitude && + other.longitude == this.longitude && + other.playbackStyle == this.playbackStyle); +} + +class LocalAssetEntityCompanion extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationMs; + final Value id; + final Value checksum; + final Value isFavorite; + final Value orientation; + final Value iCloudId; + final Value adjustmentTime; + final Value latitude; + final Value longitude; + final Value playbackStyle; + const LocalAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationMs = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + this.iCloudId = const Value.absent(), + this.adjustmentTime = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.playbackStyle = const Value.absent(), + }); + LocalAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationMs = const Value.absent(), + required String id, + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + this.iCloudId = const Value.absent(), + this.adjustmentTime = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.playbackStyle = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationMs, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? orientation, + Expression? iCloudId, + Expression? adjustmentTime, + Expression? latitude, + Expression? longitude, + Expression? playbackStyle, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationMs != null) 'duration_ms': durationMs, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (orientation != null) 'orientation': orientation, + if (iCloudId != null) 'i_cloud_id': iCloudId, + if (adjustmentTime != null) 'adjustment_time': adjustmentTime, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + if (playbackStyle != null) 'playback_style': playbackStyle, + }); + } + + LocalAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationMs, + Value? id, + Value? checksum, + Value? isFavorite, + Value? orientation, + Value? iCloudId, + Value? adjustmentTime, + Value? latitude, + Value? longitude, + Value? playbackStyle, + }) { + return LocalAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationMs: durationMs ?? this.durationMs, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + iCloudId: iCloudId ?? this.iCloudId, + adjustmentTime: adjustmentTime ?? this.adjustmentTime, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + playbackStyle: playbackStyle ?? this.playbackStyle, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationMs.present) { + map['duration_ms'] = Variable(durationMs.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + if (iCloudId.present) { + map['i_cloud_id'] = Variable(iCloudId.value); + } + if (adjustmentTime.present) { + map['adjustment_time'] = Variable(adjustmentTime.value); + } + if (latitude.present) { + map['latitude'] = Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = Variable(longitude.value); + } + if (playbackStyle.present) { + map['playback_style'] = Variable(playbackStyle.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationMs: $durationMs, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation, ') + ..write('iCloudId: $iCloudId, ') + ..write('adjustmentTime: $adjustmentTime, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('playbackStyle: $playbackStyle') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT \'\'', + defaultValue: const CustomExpression('\'\''), + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn thumbnailAssetId = GeneratedColumn( + 'thumbnail_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: + 'NULL REFERENCES remote_asset_entity(id)ON DELETE SET NULL', + ); + late final GeneratedColumn isActivityEnabled = GeneratedColumn( + 'is_activity_enabled', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: + 'NOT NULL DEFAULT 1 CHECK (is_activity_enabled IN (0, 1))', + defaultValue: const CustomExpression('1'), + ); + late final GeneratedColumn order = GeneratedColumn( + 'order', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + @override + List get $columns => [ + id, + name, + description, + createdAt, + updatedAt, + thumbnailAssetId, + isActivityEnabled, + order, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_entity'; + @override + Set get $primaryKey => {id}; + @override + RemoteAlbumEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + thumbnailAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumbnail_asset_id'], + ), + isActivityEnabled: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_activity_enabled'], + )!, + order: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}order'], + )!, + ); + } + + @override + RemoteAlbumEntity createAlias(String alias) { + return RemoteAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class RemoteAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String description; + final String createdAt; + final String updatedAt; + final String? thumbnailAssetId; + final int isActivityEnabled; + final int order; + const RemoteAlbumEntityData({ + required this.id, + required this.name, + required this.description, + required this.createdAt, + required this.updatedAt, + this.thumbnailAssetId, + required this.isActivityEnabled, + required this.order, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['description'] = Variable(description); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || thumbnailAssetId != null) { + map['thumbnail_asset_id'] = Variable(thumbnailAssetId); + } + map['is_activity_enabled'] = Variable(isActivityEnabled); + map['order'] = Variable(order); + return map; + } + + factory RemoteAlbumEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + description: serializer.fromJson(json['description']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + thumbnailAssetId: serializer.fromJson(json['thumbnailAssetId']), + isActivityEnabled: serializer.fromJson(json['isActivityEnabled']), + order: serializer.fromJson(json['order']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'description': serializer.toJson(description), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'thumbnailAssetId': serializer.toJson(thumbnailAssetId), + 'isActivityEnabled': serializer.toJson(isActivityEnabled), + 'order': serializer.toJson(order), + }; + } + + RemoteAlbumEntityData copyWith({ + String? id, + String? name, + String? description, + String? createdAt, + String? updatedAt, + Value thumbnailAssetId = const Value.absent(), + int? isActivityEnabled, + int? order, + }) => RemoteAlbumEntityData( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + thumbnailAssetId: thumbnailAssetId.present + ? thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + RemoteAlbumEntityData copyWithCompanion(RemoteAlbumEntityCompanion data) { + return RemoteAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + description: data.description.present + ? data.description.value + : this.description, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + thumbnailAssetId: data.thumbnailAssetId.present + ? data.thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: data.isActivityEnabled.present + ? data.isActivityEnabled.value + : this.isActivityEnabled, + order: data.order.present ? data.order.value : this.order, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + description, + createdAt, + updatedAt, + thumbnailAssetId, + isActivityEnabled, + order, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.description == this.description && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.thumbnailAssetId == this.thumbnailAssetId && + other.isActivityEnabled == this.isActivityEnabled && + other.order == this.order); +} + +class RemoteAlbumEntityCompanion + extends UpdateCompanion { + final Value id; + final Value name; + final Value description; + final Value createdAt; + final Value updatedAt; + final Value thumbnailAssetId; + final Value isActivityEnabled; + final Value order; + const RemoteAlbumEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.description = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + this.order = const Value.absent(), + }); + RemoteAlbumEntityCompanion.insert({ + required String id, + required String name, + this.description = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + required int order, + }) : id = Value(id), + name = Value(name), + order = Value(order); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? description, + Expression? createdAt, + Expression? updatedAt, + Expression? thumbnailAssetId, + Expression? isActivityEnabled, + Expression? order, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (description != null) 'description': description, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (thumbnailAssetId != null) 'thumbnail_asset_id': thumbnailAssetId, + if (isActivityEnabled != null) 'is_activity_enabled': isActivityEnabled, + if (order != null) 'order': order, + }); + } + + RemoteAlbumEntityCompanion copyWith({ + Value? id, + Value? name, + Value? description, + Value? createdAt, + Value? updatedAt, + Value? thumbnailAssetId, + Value? isActivityEnabled, + Value? order, + }) { + return RemoteAlbumEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + thumbnailAssetId: thumbnailAssetId ?? this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (thumbnailAssetId.present) { + map['thumbnail_asset_id'] = Variable(thumbnailAssetId.value); + } + if (isActivityEnabled.present) { + map['is_activity_enabled'] = Variable(isActivityEnabled.value); + } + if (order.present) { + map['order'] = Variable(order.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } +} + +class LocalAlbumEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAlbumEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn backupSelection = GeneratedColumn( + 'backup_selection', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn isIosSharedAlbum = GeneratedColumn( + 'is_ios_shared_album', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: + 'NOT NULL DEFAULT 0 CHECK (is_ios_shared_album IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn linkedRemoteAlbumId = + GeneratedColumn( + 'linked_remote_album_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: + 'NULL REFERENCES remote_album_entity(id)ON DELETE SET NULL', + ); + late final GeneratedColumn marker = GeneratedColumn( + 'marker', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL CHECK (marker IN (0, 1))', + ); + @override + List get $columns => [ + id, + name, + updatedAt, + backupSelection, + isIosSharedAlbum, + linkedRemoteAlbumId, + marker, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_entity'; + @override + Set get $primaryKey => {id}; + @override + LocalAlbumEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAlbumEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + backupSelection: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}backup_selection'], + )!, + isIosSharedAlbum: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_ios_shared_album'], + )!, + linkedRemoteAlbumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}linked_remote_album_id'], + ), + marker: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}marker'], + ), + ); + } + + @override + LocalAlbumEntity createAlias(String alias) { + return LocalAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class LocalAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String updatedAt; + final int backupSelection; + final int isIosSharedAlbum; + final String? linkedRemoteAlbumId; + final int? marker; + const LocalAlbumEntityData({ + required this.id, + required this.name, + required this.updatedAt, + required this.backupSelection, + required this.isIosSharedAlbum, + this.linkedRemoteAlbumId, + this.marker, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['updated_at'] = Variable(updatedAt); + map['backup_selection'] = Variable(backupSelection); + map['is_ios_shared_album'] = Variable(isIosSharedAlbum); + if (!nullToAbsent || linkedRemoteAlbumId != null) { + map['linked_remote_album_id'] = Variable(linkedRemoteAlbumId); + } + if (!nullToAbsent || marker != null) { + map['marker'] = Variable(marker); + } + return map; + } + + factory LocalAlbumEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + updatedAt: serializer.fromJson(json['updatedAt']), + backupSelection: serializer.fromJson(json['backupSelection']), + isIosSharedAlbum: serializer.fromJson(json['isIosSharedAlbum']), + linkedRemoteAlbumId: serializer.fromJson( + json['linkedRemoteAlbumId'], + ), + marker: serializer.fromJson(json['marker']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'updatedAt': serializer.toJson(updatedAt), + 'backupSelection': serializer.toJson(backupSelection), + 'isIosSharedAlbum': serializer.toJson(isIosSharedAlbum), + 'linkedRemoteAlbumId': serializer.toJson(linkedRemoteAlbumId), + 'marker': serializer.toJson(marker), + }; + } + + LocalAlbumEntityData copyWith({ + String? id, + String? name, + String? updatedAt, + int? backupSelection, + int? isIosSharedAlbum, + Value linkedRemoteAlbumId = const Value.absent(), + Value marker = const Value.absent(), + }) => LocalAlbumEntityData( + id: id ?? this.id, + name: name ?? this.name, + updatedAt: updatedAt ?? this.updatedAt, + backupSelection: backupSelection ?? this.backupSelection, + isIosSharedAlbum: isIosSharedAlbum ?? this.isIosSharedAlbum, + linkedRemoteAlbumId: linkedRemoteAlbumId.present + ? linkedRemoteAlbumId.value + : this.linkedRemoteAlbumId, + marker: marker.present ? marker.value : this.marker, + ); + LocalAlbumEntityData copyWithCompanion(LocalAlbumEntityCompanion data) { + return LocalAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + backupSelection: data.backupSelection.present + ? data.backupSelection.value + : this.backupSelection, + isIosSharedAlbum: data.isIosSharedAlbum.present + ? data.isIosSharedAlbum.value + : this.isIosSharedAlbum, + linkedRemoteAlbumId: data.linkedRemoteAlbumId.present + ? data.linkedRemoteAlbumId.value + : this.linkedRemoteAlbumId, + marker: data.marker.present ? data.marker.value : this.marker, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('updatedAt: $updatedAt, ') + ..write('backupSelection: $backupSelection, ') + ..write('isIosSharedAlbum: $isIosSharedAlbum, ') + ..write('linkedRemoteAlbumId: $linkedRemoteAlbumId, ') + ..write('marker: $marker') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + updatedAt, + backupSelection, + isIosSharedAlbum, + linkedRemoteAlbumId, + marker, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.updatedAt == this.updatedAt && + other.backupSelection == this.backupSelection && + other.isIosSharedAlbum == this.isIosSharedAlbum && + other.linkedRemoteAlbumId == this.linkedRemoteAlbumId && + other.marker == this.marker); +} + +class LocalAlbumEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value updatedAt; + final Value backupSelection; + final Value isIosSharedAlbum; + final Value linkedRemoteAlbumId; + final Value marker; + const LocalAlbumEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.updatedAt = const Value.absent(), + this.backupSelection = const Value.absent(), + this.isIosSharedAlbum = const Value.absent(), + this.linkedRemoteAlbumId = const Value.absent(), + this.marker = const Value.absent(), + }); + LocalAlbumEntityCompanion.insert({ + required String id, + required String name, + this.updatedAt = const Value.absent(), + required int backupSelection, + this.isIosSharedAlbum = const Value.absent(), + this.linkedRemoteAlbumId = const Value.absent(), + this.marker = const Value.absent(), + }) : id = Value(id), + name = Value(name), + backupSelection = Value(backupSelection); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? updatedAt, + Expression? backupSelection, + Expression? isIosSharedAlbum, + Expression? linkedRemoteAlbumId, + Expression? marker, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (updatedAt != null) 'updated_at': updatedAt, + if (backupSelection != null) 'backup_selection': backupSelection, + if (isIosSharedAlbum != null) 'is_ios_shared_album': isIosSharedAlbum, + if (linkedRemoteAlbumId != null) + 'linked_remote_album_id': linkedRemoteAlbumId, + if (marker != null) 'marker': marker, + }); + } + + LocalAlbumEntityCompanion copyWith({ + Value? id, + Value? name, + Value? updatedAt, + Value? backupSelection, + Value? isIosSharedAlbum, + Value? linkedRemoteAlbumId, + Value? marker, + }) { + return LocalAlbumEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + updatedAt: updatedAt ?? this.updatedAt, + backupSelection: backupSelection ?? this.backupSelection, + isIosSharedAlbum: isIosSharedAlbum ?? this.isIosSharedAlbum, + linkedRemoteAlbumId: linkedRemoteAlbumId ?? this.linkedRemoteAlbumId, + marker: marker ?? this.marker, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (backupSelection.present) { + map['backup_selection'] = Variable(backupSelection.value); + } + if (isIosSharedAlbum.present) { + map['is_ios_shared_album'] = Variable(isIosSharedAlbum.value); + } + if (linkedRemoteAlbumId.present) { + map['linked_remote_album_id'] = Variable( + linkedRemoteAlbumId.value, + ); + } + if (marker.present) { + map['marker'] = Variable(marker.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('updatedAt: $updatedAt, ') + ..write('backupSelection: $backupSelection, ') + ..write('isIosSharedAlbum: $isIosSharedAlbum, ') + ..write('linkedRemoteAlbumId: $linkedRemoteAlbumId, ') + ..write('marker: $marker') + ..write(')')) + .toString(); + } +} + +class LocalAlbumAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAlbumAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES local_asset_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES local_album_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn marker = GeneratedColumn( + 'marker', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL CHECK (marker IN (0, 1))', + ); + @override + List get $columns => [assetId, albumId, marker]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_asset_entity'; + @override + Set get $primaryKey => {assetId, albumId}; + @override + LocalAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + marker: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}marker'], + ), + ); + } + + @override + LocalAlbumAssetEntity createAlias(String alias) { + return LocalAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const [ + 'PRIMARY KEY(asset_id, album_id)', + ]; + @override + bool get dontWriteConstraints => true; +} + +class LocalAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + final int? marker; + const LocalAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + this.marker, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['album_id'] = Variable(albumId); + if (!nullToAbsent || marker != null) { + map['marker'] = Variable(marker); + } + return map; + } + + factory LocalAlbumAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + marker: serializer.fromJson(json['marker']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + 'marker': serializer.toJson(marker), + }; + } + + LocalAlbumAssetEntityData copyWith({ + String? assetId, + String? albumId, + Value marker = const Value.absent(), + }) => LocalAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + marker: marker.present ? marker.value : this.marker, + ); + LocalAlbumAssetEntityData copyWithCompanion( + LocalAlbumAssetEntityCompanion data, + ) { + return LocalAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + marker: data.marker.present ? data.marker.value : this.marker, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId, ') + ..write('marker: $marker') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId, marker); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId && + other.marker == this.marker); +} + +class LocalAlbumAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value albumId; + final Value marker; + const LocalAlbumAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.albumId = const Value.absent(), + this.marker = const Value.absent(), + }); + LocalAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + this.marker = const Value.absent(), + }) : assetId = Value(assetId), + albumId = Value(albumId); + static Insertable custom({ + Expression? assetId, + Expression? albumId, + Expression? marker, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + if (marker != null) 'marker': marker, + }); + } + + LocalAlbumAssetEntityCompanion copyWith({ + Value? assetId, + Value? albumId, + Value? marker, + }) { + return LocalAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + marker: marker ?? this.marker, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (marker.present) { + map['marker'] = Variable(marker.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId, ') + ..write('marker: $marker') + ..write(')')) + .toString(); + } +} + +class AuthUserEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AuthUserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn email = GeneratedColumn( + 'email', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn isAdmin = GeneratedColumn( + 'is_admin', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0 CHECK (is_admin IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn hasProfileImage = GeneratedColumn( + 'has_profile_image', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: + 'NOT NULL DEFAULT 0 CHECK (has_profile_image IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn profileChangedAt = GeneratedColumn( + 'profile_changed_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn avatarColor = GeneratedColumn( + 'avatar_color', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn quotaSizeInBytes = GeneratedColumn( + 'quota_size_in_bytes', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn quotaUsageInBytes = GeneratedColumn( + 'quota_usage_in_bytes', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn pinCode = GeneratedColumn( + 'pin_code', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + @override + List get $columns => [ + id, + name, + email, + isAdmin, + hasProfileImage, + profileChangedAt, + avatarColor, + quotaSizeInBytes, + quotaUsageInBytes, + pinCode, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'auth_user_entity'; + @override + Set get $primaryKey => {id}; + @override + AuthUserEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AuthUserEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + email: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}email'], + )!, + isAdmin: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_admin'], + )!, + hasProfileImage: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}has_profile_image'], + )!, + profileChangedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}profile_changed_at'], + )!, + avatarColor: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}avatar_color'], + )!, + quotaSizeInBytes: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}quota_size_in_bytes'], + )!, + quotaUsageInBytes: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}quota_usage_in_bytes'], + )!, + pinCode: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}pin_code'], + ), + ); + } + + @override + AuthUserEntity createAlias(String alias) { + return AuthUserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class AuthUserEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String email; + final int isAdmin; + final int hasProfileImage; + final String profileChangedAt; + final int avatarColor; + final int quotaSizeInBytes; + final int quotaUsageInBytes; + final String? pinCode; + const AuthUserEntityData({ + required this.id, + required this.name, + required this.email, + required this.isAdmin, + required this.hasProfileImage, + required this.profileChangedAt, + required this.avatarColor, + required this.quotaSizeInBytes, + required this.quotaUsageInBytes, + this.pinCode, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['email'] = Variable(email); + map['is_admin'] = Variable(isAdmin); + map['has_profile_image'] = Variable(hasProfileImage); + map['profile_changed_at'] = Variable(profileChangedAt); + map['avatar_color'] = Variable(avatarColor); + map['quota_size_in_bytes'] = Variable(quotaSizeInBytes); + map['quota_usage_in_bytes'] = Variable(quotaUsageInBytes); + if (!nullToAbsent || pinCode != null) { + map['pin_code'] = Variable(pinCode); + } + return map; + } + + factory AuthUserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AuthUserEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + email: serializer.fromJson(json['email']), + isAdmin: serializer.fromJson(json['isAdmin']), + hasProfileImage: serializer.fromJson(json['hasProfileImage']), + profileChangedAt: serializer.fromJson(json['profileChangedAt']), + avatarColor: serializer.fromJson(json['avatarColor']), + quotaSizeInBytes: serializer.fromJson(json['quotaSizeInBytes']), + quotaUsageInBytes: serializer.fromJson(json['quotaUsageInBytes']), + pinCode: serializer.fromJson(json['pinCode']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'email': serializer.toJson(email), + 'isAdmin': serializer.toJson(isAdmin), + 'hasProfileImage': serializer.toJson(hasProfileImage), + 'profileChangedAt': serializer.toJson(profileChangedAt), + 'avatarColor': serializer.toJson(avatarColor), + 'quotaSizeInBytes': serializer.toJson(quotaSizeInBytes), + 'quotaUsageInBytes': serializer.toJson(quotaUsageInBytes), + 'pinCode': serializer.toJson(pinCode), + }; + } + + AuthUserEntityData copyWith({ + String? id, + String? name, + String? email, + int? isAdmin, + int? hasProfileImage, + String? profileChangedAt, + int? avatarColor, + int? quotaSizeInBytes, + int? quotaUsageInBytes, + Value pinCode = const Value.absent(), + }) => AuthUserEntityData( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + isAdmin: isAdmin ?? this.isAdmin, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + quotaSizeInBytes: quotaSizeInBytes ?? this.quotaSizeInBytes, + quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes, + pinCode: pinCode.present ? pinCode.value : this.pinCode, + ); + AuthUserEntityData copyWithCompanion(AuthUserEntityCompanion data) { + return AuthUserEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + email: data.email.present ? data.email.value : this.email, + isAdmin: data.isAdmin.present ? data.isAdmin.value : this.isAdmin, + hasProfileImage: data.hasProfileImage.present + ? data.hasProfileImage.value + : this.hasProfileImage, + profileChangedAt: data.profileChangedAt.present + ? data.profileChangedAt.value + : this.profileChangedAt, + avatarColor: data.avatarColor.present + ? data.avatarColor.value + : this.avatarColor, + quotaSizeInBytes: data.quotaSizeInBytes.present + ? data.quotaSizeInBytes.value + : this.quotaSizeInBytes, + quotaUsageInBytes: data.quotaUsageInBytes.present + ? data.quotaUsageInBytes.value + : this.quotaUsageInBytes, + pinCode: data.pinCode.present ? data.pinCode.value : this.pinCode, + ); + } + + @override + String toString() { + return (StringBuffer('AuthUserEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('isAdmin: $isAdmin, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor, ') + ..write('quotaSizeInBytes: $quotaSizeInBytes, ') + ..write('quotaUsageInBytes: $quotaUsageInBytes, ') + ..write('pinCode: $pinCode') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + email, + isAdmin, + hasProfileImage, + profileChangedAt, + avatarColor, + quotaSizeInBytes, + quotaUsageInBytes, + pinCode, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AuthUserEntityData && + other.id == this.id && + other.name == this.name && + other.email == this.email && + other.isAdmin == this.isAdmin && + other.hasProfileImage == this.hasProfileImage && + other.profileChangedAt == this.profileChangedAt && + other.avatarColor == this.avatarColor && + other.quotaSizeInBytes == this.quotaSizeInBytes && + other.quotaUsageInBytes == this.quotaUsageInBytes && + other.pinCode == this.pinCode); +} + +class AuthUserEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value email; + final Value isAdmin; + final Value hasProfileImage; + final Value profileChangedAt; + final Value avatarColor; + final Value quotaSizeInBytes; + final Value quotaUsageInBytes; + final Value pinCode; + const AuthUserEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.email = const Value.absent(), + this.isAdmin = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + this.quotaSizeInBytes = const Value.absent(), + this.quotaUsageInBytes = const Value.absent(), + this.pinCode = const Value.absent(), + }); + AuthUserEntityCompanion.insert({ + required String id, + required String name, + required String email, + this.isAdmin = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + required int avatarColor, + this.quotaSizeInBytes = const Value.absent(), + this.quotaUsageInBytes = const Value.absent(), + this.pinCode = const Value.absent(), + }) : id = Value(id), + name = Value(name), + email = Value(email), + avatarColor = Value(avatarColor); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? email, + Expression? isAdmin, + Expression? hasProfileImage, + Expression? profileChangedAt, + Expression? avatarColor, + Expression? quotaSizeInBytes, + Expression? quotaUsageInBytes, + Expression? pinCode, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (email != null) 'email': email, + if (isAdmin != null) 'is_admin': isAdmin, + if (hasProfileImage != null) 'has_profile_image': hasProfileImage, + if (profileChangedAt != null) 'profile_changed_at': profileChangedAt, + if (avatarColor != null) 'avatar_color': avatarColor, + if (quotaSizeInBytes != null) 'quota_size_in_bytes': quotaSizeInBytes, + if (quotaUsageInBytes != null) 'quota_usage_in_bytes': quotaUsageInBytes, + if (pinCode != null) 'pin_code': pinCode, + }); + } + + AuthUserEntityCompanion copyWith({ + Value? id, + Value? name, + Value? email, + Value? isAdmin, + Value? hasProfileImage, + Value? profileChangedAt, + Value? avatarColor, + Value? quotaSizeInBytes, + Value? quotaUsageInBytes, + Value? pinCode, + }) { + return AuthUserEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + isAdmin: isAdmin ?? this.isAdmin, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + quotaSizeInBytes: quotaSizeInBytes ?? this.quotaSizeInBytes, + quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes, + pinCode: pinCode ?? this.pinCode, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (email.present) { + map['email'] = Variable(email.value); + } + if (isAdmin.present) { + map['is_admin'] = Variable(isAdmin.value); + } + if (hasProfileImage.present) { + map['has_profile_image'] = Variable(hasProfileImage.value); + } + if (profileChangedAt.present) { + map['profile_changed_at'] = Variable(profileChangedAt.value); + } + if (avatarColor.present) { + map['avatar_color'] = Variable(avatarColor.value); + } + if (quotaSizeInBytes.present) { + map['quota_size_in_bytes'] = Variable(quotaSizeInBytes.value); + } + if (quotaUsageInBytes.present) { + map['quota_usage_in_bytes'] = Variable(quotaUsageInBytes.value); + } + if (pinCode.present) { + map['pin_code'] = Variable(pinCode.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AuthUserEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('isAdmin: $isAdmin, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor, ') + ..write('quotaSizeInBytes: $quotaSizeInBytes, ') + ..write('quotaUsageInBytes: $quotaUsageInBytes, ') + ..write('pinCode: $pinCode') + ..write(')')) + .toString(); + } +} + +class UserMetadataEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UserMetadataEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL REFERENCES user_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn key = GeneratedColumn( + 'key', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn value = + GeneratedColumn( + 'value', + aliasedName, + false, + type: DriftSqlType.blob, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + @override + List get $columns => [userId, key, value]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_metadata_entity'; + @override + Set get $primaryKey => {userId, key}; + @override + UserMetadataEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserMetadataEntityData( + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + key: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}key'], + )!, + value: attachedDatabase.typeMapping.read( + DriftSqlType.blob, + data['${effectivePrefix}value'], + )!, + ); + } + + @override + UserMetadataEntity createAlias(String alias) { + return UserMetadataEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(user_id, "key")']; + @override + bool get dontWriteConstraints => true; +} + +class UserMetadataEntityData extends DataClass + implements Insertable { + final String userId; + final int key; + final i2.Uint8List value; + const UserMetadataEntityData({ + required this.userId, + required this.key, + required this.value, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['user_id'] = Variable(userId); + map['key'] = Variable(key); + map['value'] = Variable(value); + return map; + } + + factory UserMetadataEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserMetadataEntityData( + userId: serializer.fromJson(json['userId']), + key: serializer.fromJson(json['key']), + value: serializer.fromJson(json['value']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'userId': serializer.toJson(userId), + 'key': serializer.toJson(key), + 'value': serializer.toJson(value), + }; + } + + UserMetadataEntityData copyWith({ + String? userId, + int? key, + i2.Uint8List? value, + }) => UserMetadataEntityData( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + UserMetadataEntityData copyWithCompanion(UserMetadataEntityCompanion data) { + return UserMetadataEntityData( + userId: data.userId.present ? data.userId.value : this.userId, + key: data.key.present ? data.key.value : this.key, + value: data.value.present ? data.value.value : this.value, + ); + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityData(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(userId, key, $driftBlobEquality.hash(value)); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserMetadataEntityData && + other.userId == this.userId && + other.key == this.key && + $driftBlobEquality.equals(other.value, this.value)); +} + +class UserMetadataEntityCompanion + extends UpdateCompanion { + final Value userId; + final Value key; + final Value value; + const UserMetadataEntityCompanion({ + this.userId = const Value.absent(), + this.key = const Value.absent(), + this.value = const Value.absent(), + }); + UserMetadataEntityCompanion.insert({ + required String userId, + required int key, + required i2.Uint8List value, + }) : userId = Value(userId), + key = Value(key), + value = Value(value); + static Insertable custom({ + Expression? userId, + Expression? key, + Expression? value, + }) { + return RawValuesInsertable({ + if (userId != null) 'user_id': userId, + if (key != null) 'key': key, + if (value != null) 'value': value, + }); + } + + UserMetadataEntityCompanion copyWith({ + Value? userId, + Value? key, + Value? value, + }) { + return UserMetadataEntityCompanion( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (key.present) { + map['key'] = Variable(key.value); + } + if (value.present) { + map['value'] = Variable(value.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityCompanion(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } +} + +class PartnerEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PartnerEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn sharedById = GeneratedColumn( + 'shared_by_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL REFERENCES user_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn sharedWithId = GeneratedColumn( + 'shared_with_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL REFERENCES user_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn inTimeline = GeneratedColumn( + 'in_timeline', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0 CHECK (in_timeline IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [sharedById, sharedWithId, inTimeline]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'partner_entity'; + @override + Set get $primaryKey => {sharedById, sharedWithId}; + @override + PartnerEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PartnerEntityData( + sharedById: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}shared_by_id'], + )!, + sharedWithId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}shared_with_id'], + )!, + inTimeline: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}in_timeline'], + )!, + ); + } + + @override + PartnerEntity createAlias(String alias) { + return PartnerEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const [ + 'PRIMARY KEY(shared_by_id, shared_with_id)', + ]; + @override + bool get dontWriteConstraints => true; +} + +class PartnerEntityData extends DataClass + implements Insertable { + final String sharedById; + final String sharedWithId; + final int inTimeline; + const PartnerEntityData({ + required this.sharedById, + required this.sharedWithId, + required this.inTimeline, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['shared_by_id'] = Variable(sharedById); + map['shared_with_id'] = Variable(sharedWithId); + map['in_timeline'] = Variable(inTimeline); + return map; + } + + factory PartnerEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PartnerEntityData( + sharedById: serializer.fromJson(json['sharedById']), + sharedWithId: serializer.fromJson(json['sharedWithId']), + inTimeline: serializer.fromJson(json['inTimeline']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'sharedById': serializer.toJson(sharedById), + 'sharedWithId': serializer.toJson(sharedWithId), + 'inTimeline': serializer.toJson(inTimeline), + }; + } + + PartnerEntityData copyWith({ + String? sharedById, + String? sharedWithId, + int? inTimeline, + }) => PartnerEntityData( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + PartnerEntityData copyWithCompanion(PartnerEntityCompanion data) { + return PartnerEntityData( + sharedById: data.sharedById.present + ? data.sharedById.value + : this.sharedById, + sharedWithId: data.sharedWithId.present + ? data.sharedWithId.value + : this.sharedWithId, + inTimeline: data.inTimeline.present + ? data.inTimeline.value + : this.inTimeline, + ); + } + + @override + String toString() { + return (StringBuffer('PartnerEntityData(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(sharedById, sharedWithId, inTimeline); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PartnerEntityData && + other.sharedById == this.sharedById && + other.sharedWithId == this.sharedWithId && + other.inTimeline == this.inTimeline); +} + +class PartnerEntityCompanion extends UpdateCompanion { + final Value sharedById; + final Value sharedWithId; + final Value inTimeline; + const PartnerEntityCompanion({ + this.sharedById = const Value.absent(), + this.sharedWithId = const Value.absent(), + this.inTimeline = const Value.absent(), + }); + PartnerEntityCompanion.insert({ + required String sharedById, + required String sharedWithId, + this.inTimeline = const Value.absent(), + }) : sharedById = Value(sharedById), + sharedWithId = Value(sharedWithId); + static Insertable custom({ + Expression? sharedById, + Expression? sharedWithId, + Expression? inTimeline, + }) { + return RawValuesInsertable({ + if (sharedById != null) 'shared_by_id': sharedById, + if (sharedWithId != null) 'shared_with_id': sharedWithId, + if (inTimeline != null) 'in_timeline': inTimeline, + }); + } + + PartnerEntityCompanion copyWith({ + Value? sharedById, + Value? sharedWithId, + Value? inTimeline, + }) { + return PartnerEntityCompanion( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (sharedById.present) { + map['shared_by_id'] = Variable(sharedById.value); + } + if (sharedWithId.present) { + map['shared_with_id'] = Variable(sharedWithId.value); + } + if (inTimeline.present) { + map['in_timeline'] = Variable(inTimeline.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PartnerEntityCompanion(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } +} + +class RemoteExifEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteExifEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES remote_asset_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn city = GeneratedColumn( + 'city', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn state = GeneratedColumn( + 'state', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn country = GeneratedColumn( + 'country', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn dateTimeOriginal = GeneratedColumn( + 'date_time_original', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn exposureTime = GeneratedColumn( + 'exposure_time', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn fNumber = GeneratedColumn( + 'f_number', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn fileSize = GeneratedColumn( + 'file_size', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn focalLength = GeneratedColumn( + 'focal_length', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn iso = GeneratedColumn( + 'iso', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn make = GeneratedColumn( + 'make', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn model = GeneratedColumn( + 'model', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn lens = GeneratedColumn( + 'lens', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn timeZone = GeneratedColumn( + 'time_zone', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn rating = GeneratedColumn( + 'rating', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn projectionType = GeneratedColumn( + 'projection_type', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + @override + List get $columns => [ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_exif_entity'; + @override + Set get $primaryKey => {assetId}; + @override + RemoteExifEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteExifEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + city: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}city'], + ), + state: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}state'], + ), + country: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}country'], + ), + dateTimeOriginal: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}date_time_original'], + ), + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + exposureTime: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}exposure_time'], + ), + fNumber: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}f_number'], + ), + fileSize: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}file_size'], + ), + focalLength: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}focal_length'], + ), + latitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + iso: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}iso'], + ), + make: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}make'], + ), + model: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}model'], + ), + lens: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}lens'], + ), + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}orientation'], + ), + timeZone: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}time_zone'], + ), + rating: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}rating'], + ), + projectionType: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}projection_type'], + ), + ); + } + + @override + RemoteExifEntity createAlias(String alias) { + return RemoteExifEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(asset_id)']; + @override + bool get dontWriteConstraints => true; +} + +class RemoteExifEntityData extends DataClass + implements Insertable { + final String assetId; + final String? city; + final String? state; + final String? country; + final String? dateTimeOriginal; + final String? description; + final int? height; + final int? width; + final String? exposureTime; + final double? fNumber; + final int? fileSize; + final double? focalLength; + final double? latitude; + final double? longitude; + final int? iso; + final String? make; + final String? model; + final String? lens; + final String? orientation; + final String? timeZone; + final int? rating; + final String? projectionType; + const RemoteExifEntityData({ + required this.assetId, + this.city, + this.state, + this.country, + this.dateTimeOriginal, + this.description, + this.height, + this.width, + this.exposureTime, + this.fNumber, + this.fileSize, + this.focalLength, + this.latitude, + this.longitude, + this.iso, + this.make, + this.model, + this.lens, + this.orientation, + this.timeZone, + this.rating, + this.projectionType, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || city != null) { + map['city'] = Variable(city); + } + if (!nullToAbsent || state != null) { + map['state'] = Variable(state); + } + if (!nullToAbsent || country != null) { + map['country'] = Variable(country); + } + if (!nullToAbsent || dateTimeOriginal != null) { + map['date_time_original'] = Variable(dateTimeOriginal); + } + if (!nullToAbsent || description != null) { + map['description'] = Variable(description); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || exposureTime != null) { + map['exposure_time'] = Variable(exposureTime); + } + if (!nullToAbsent || fNumber != null) { + map['f_number'] = Variable(fNumber); + } + if (!nullToAbsent || fileSize != null) { + map['file_size'] = Variable(fileSize); + } + if (!nullToAbsent || focalLength != null) { + map['focal_length'] = Variable(focalLength); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = Variable(longitude); + } + if (!nullToAbsent || iso != null) { + map['iso'] = Variable(iso); + } + if (!nullToAbsent || make != null) { + map['make'] = Variable(make); + } + if (!nullToAbsent || model != null) { + map['model'] = Variable(model); + } + if (!nullToAbsent || lens != null) { + map['lens'] = Variable(lens); + } + if (!nullToAbsent || orientation != null) { + map['orientation'] = Variable(orientation); + } + if (!nullToAbsent || timeZone != null) { + map['time_zone'] = Variable(timeZone); + } + if (!nullToAbsent || rating != null) { + map['rating'] = Variable(rating); + } + if (!nullToAbsent || projectionType != null) { + map['projection_type'] = Variable(projectionType); + } + return map; + } + + factory RemoteExifEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteExifEntityData( + assetId: serializer.fromJson(json['assetId']), + city: serializer.fromJson(json['city']), + state: serializer.fromJson(json['state']), + country: serializer.fromJson(json['country']), + dateTimeOriginal: serializer.fromJson(json['dateTimeOriginal']), + description: serializer.fromJson(json['description']), + height: serializer.fromJson(json['height']), + width: serializer.fromJson(json['width']), + exposureTime: serializer.fromJson(json['exposureTime']), + fNumber: serializer.fromJson(json['fNumber']), + fileSize: serializer.fromJson(json['fileSize']), + focalLength: serializer.fromJson(json['focalLength']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + iso: serializer.fromJson(json['iso']), + make: serializer.fromJson(json['make']), + model: serializer.fromJson(json['model']), + lens: serializer.fromJson(json['lens']), + orientation: serializer.fromJson(json['orientation']), + timeZone: serializer.fromJson(json['timeZone']), + rating: serializer.fromJson(json['rating']), + projectionType: serializer.fromJson(json['projectionType']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'city': serializer.toJson(city), + 'state': serializer.toJson(state), + 'country': serializer.toJson(country), + 'dateTimeOriginal': serializer.toJson(dateTimeOriginal), + 'description': serializer.toJson(description), + 'height': serializer.toJson(height), + 'width': serializer.toJson(width), + 'exposureTime': serializer.toJson(exposureTime), + 'fNumber': serializer.toJson(fNumber), + 'fileSize': serializer.toJson(fileSize), + 'focalLength': serializer.toJson(focalLength), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + 'iso': serializer.toJson(iso), + 'make': serializer.toJson(make), + 'model': serializer.toJson(model), + 'lens': serializer.toJson(lens), + 'orientation': serializer.toJson(orientation), + 'timeZone': serializer.toJson(timeZone), + 'rating': serializer.toJson(rating), + 'projectionType': serializer.toJson(projectionType), + }; + } + + RemoteExifEntityData copyWith({ + String? assetId, + Value city = const Value.absent(), + Value state = const Value.absent(), + Value country = const Value.absent(), + Value dateTimeOriginal = const Value.absent(), + Value description = const Value.absent(), + Value height = const Value.absent(), + Value width = const Value.absent(), + Value exposureTime = const Value.absent(), + Value fNumber = const Value.absent(), + Value fileSize = const Value.absent(), + Value focalLength = const Value.absent(), + Value latitude = const Value.absent(), + Value longitude = const Value.absent(), + Value iso = const Value.absent(), + Value make = const Value.absent(), + Value model = const Value.absent(), + Value lens = const Value.absent(), + Value orientation = const Value.absent(), + Value timeZone = const Value.absent(), + Value rating = const Value.absent(), + Value projectionType = const Value.absent(), + }) => RemoteExifEntityData( + assetId: assetId ?? this.assetId, + city: city.present ? city.value : this.city, + state: state.present ? state.value : this.state, + country: country.present ? country.value : this.country, + dateTimeOriginal: dateTimeOriginal.present + ? dateTimeOriginal.value + : this.dateTimeOriginal, + description: description.present ? description.value : this.description, + height: height.present ? height.value : this.height, + width: width.present ? width.value : this.width, + exposureTime: exposureTime.present ? exposureTime.value : this.exposureTime, + fNumber: fNumber.present ? fNumber.value : this.fNumber, + fileSize: fileSize.present ? fileSize.value : this.fileSize, + focalLength: focalLength.present ? focalLength.value : this.focalLength, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + iso: iso.present ? iso.value : this.iso, + make: make.present ? make.value : this.make, + model: model.present ? model.value : this.model, + lens: lens.present ? lens.value : this.lens, + orientation: orientation.present ? orientation.value : this.orientation, + timeZone: timeZone.present ? timeZone.value : this.timeZone, + rating: rating.present ? rating.value : this.rating, + projectionType: projectionType.present + ? projectionType.value + : this.projectionType, + ); + RemoteExifEntityData copyWithCompanion(RemoteExifEntityCompanion data) { + return RemoteExifEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + city: data.city.present ? data.city.value : this.city, + state: data.state.present ? data.state.value : this.state, + country: data.country.present ? data.country.value : this.country, + dateTimeOriginal: data.dateTimeOriginal.present + ? data.dateTimeOriginal.value + : this.dateTimeOriginal, + description: data.description.present + ? data.description.value + : this.description, + height: data.height.present ? data.height.value : this.height, + width: data.width.present ? data.width.value : this.width, + exposureTime: data.exposureTime.present + ? data.exposureTime.value + : this.exposureTime, + fNumber: data.fNumber.present ? data.fNumber.value : this.fNumber, + fileSize: data.fileSize.present ? data.fileSize.value : this.fileSize, + focalLength: data.focalLength.present + ? data.focalLength.value + : this.focalLength, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + iso: data.iso.present ? data.iso.value : this.iso, + make: data.make.present ? data.make.value : this.make, + model: data.model.present ? data.model.value : this.model, + lens: data.lens.present ? data.lens.value : this.lens, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + timeZone: data.timeZone.present ? data.timeZone.value : this.timeZone, + rating: data.rating.present ? data.rating.value : this.rating, + projectionType: data.projectionType.present + ? data.projectionType.value + : this.projectionType, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityData(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hashAll([ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteExifEntityData && + other.assetId == this.assetId && + other.city == this.city && + other.state == this.state && + other.country == this.country && + other.dateTimeOriginal == this.dateTimeOriginal && + other.description == this.description && + other.height == this.height && + other.width == this.width && + other.exposureTime == this.exposureTime && + other.fNumber == this.fNumber && + other.fileSize == this.fileSize && + other.focalLength == this.focalLength && + other.latitude == this.latitude && + other.longitude == this.longitude && + other.iso == this.iso && + other.make == this.make && + other.model == this.model && + other.lens == this.lens && + other.orientation == this.orientation && + other.timeZone == this.timeZone && + other.rating == this.rating && + other.projectionType == this.projectionType); +} + +class RemoteExifEntityCompanion extends UpdateCompanion { + final Value assetId; + final Value city; + final Value state; + final Value country; + final Value dateTimeOriginal; + final Value description; + final Value height; + final Value width; + final Value exposureTime; + final Value fNumber; + final Value fileSize; + final Value focalLength; + final Value latitude; + final Value longitude; + final Value iso; + final Value make; + final Value model; + final Value lens; + final Value orientation; + final Value timeZone; + final Value rating; + final Value projectionType; + const RemoteExifEntityCompanion({ + this.assetId = const Value.absent(), + this.city = const Value.absent(), + this.state = const Value.absent(), + this.country = const Value.absent(), + this.dateTimeOriginal = const Value.absent(), + this.description = const Value.absent(), + this.height = const Value.absent(), + this.width = const Value.absent(), + this.exposureTime = const Value.absent(), + this.fNumber = const Value.absent(), + this.fileSize = const Value.absent(), + this.focalLength = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.iso = const Value.absent(), + this.make = const Value.absent(), + this.model = const Value.absent(), + this.lens = const Value.absent(), + this.orientation = const Value.absent(), + this.timeZone = const Value.absent(), + this.rating = const Value.absent(), + this.projectionType = const Value.absent(), + }); + RemoteExifEntityCompanion.insert({ + required String assetId, + this.city = const Value.absent(), + this.state = const Value.absent(), + this.country = const Value.absent(), + this.dateTimeOriginal = const Value.absent(), + this.description = const Value.absent(), + this.height = const Value.absent(), + this.width = const Value.absent(), + this.exposureTime = const Value.absent(), + this.fNumber = const Value.absent(), + this.fileSize = const Value.absent(), + this.focalLength = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.iso = const Value.absent(), + this.make = const Value.absent(), + this.model = const Value.absent(), + this.lens = const Value.absent(), + this.orientation = const Value.absent(), + this.timeZone = const Value.absent(), + this.rating = const Value.absent(), + this.projectionType = const Value.absent(), + }) : assetId = Value(assetId); + static Insertable custom({ + Expression? assetId, + Expression? city, + Expression? state, + Expression? country, + Expression? dateTimeOriginal, + Expression? description, + Expression? height, + Expression? width, + Expression? exposureTime, + Expression? fNumber, + Expression? fileSize, + Expression? focalLength, + Expression? latitude, + Expression? longitude, + Expression? iso, + Expression? make, + Expression? model, + Expression? lens, + Expression? orientation, + Expression? timeZone, + Expression? rating, + Expression? projectionType, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (city != null) 'city': city, + if (state != null) 'state': state, + if (country != null) 'country': country, + if (dateTimeOriginal != null) 'date_time_original': dateTimeOriginal, + if (description != null) 'description': description, + if (height != null) 'height': height, + if (width != null) 'width': width, + if (exposureTime != null) 'exposure_time': exposureTime, + if (fNumber != null) 'f_number': fNumber, + if (fileSize != null) 'file_size': fileSize, + if (focalLength != null) 'focal_length': focalLength, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + if (iso != null) 'iso': iso, + if (make != null) 'make': make, + if (model != null) 'model': model, + if (lens != null) 'lens': lens, + if (orientation != null) 'orientation': orientation, + if (timeZone != null) 'time_zone': timeZone, + if (rating != null) 'rating': rating, + if (projectionType != null) 'projection_type': projectionType, + }); + } + + RemoteExifEntityCompanion copyWith({ + Value? assetId, + Value? city, + Value? state, + Value? country, + Value? dateTimeOriginal, + Value? description, + Value? height, + Value? width, + Value? exposureTime, + Value? fNumber, + Value? fileSize, + Value? focalLength, + Value? latitude, + Value? longitude, + Value? iso, + Value? make, + Value? model, + Value? lens, + Value? orientation, + Value? timeZone, + Value? rating, + Value? projectionType, + }) { + return RemoteExifEntityCompanion( + assetId: assetId ?? this.assetId, + city: city ?? this.city, + state: state ?? this.state, + country: country ?? this.country, + dateTimeOriginal: dateTimeOriginal ?? this.dateTimeOriginal, + description: description ?? this.description, + height: height ?? this.height, + width: width ?? this.width, + exposureTime: exposureTime ?? this.exposureTime, + fNumber: fNumber ?? this.fNumber, + fileSize: fileSize ?? this.fileSize, + focalLength: focalLength ?? this.focalLength, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + iso: iso ?? this.iso, + make: make ?? this.make, + model: model ?? this.model, + lens: lens ?? this.lens, + orientation: orientation ?? this.orientation, + timeZone: timeZone ?? this.timeZone, + rating: rating ?? this.rating, + projectionType: projectionType ?? this.projectionType, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (city.present) { + map['city'] = Variable(city.value); + } + if (state.present) { + map['state'] = Variable(state.value); + } + if (country.present) { + map['country'] = Variable(country.value); + } + if (dateTimeOriginal.present) { + map['date_time_original'] = Variable(dateTimeOriginal.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (exposureTime.present) { + map['exposure_time'] = Variable(exposureTime.value); + } + if (fNumber.present) { + map['f_number'] = Variable(fNumber.value); + } + if (fileSize.present) { + map['file_size'] = Variable(fileSize.value); + } + if (focalLength.present) { + map['focal_length'] = Variable(focalLength.value); + } + if (latitude.present) { + map['latitude'] = Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = Variable(longitude.value); + } + if (iso.present) { + map['iso'] = Variable(iso.value); + } + if (make.present) { + map['make'] = Variable(make.value); + } + if (model.present) { + map['model'] = Variable(model.value); + } + if (lens.present) { + map['lens'] = Variable(lens.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + if (timeZone.present) { + map['time_zone'] = Variable(timeZone.value); + } + if (rating.present) { + map['rating'] = Variable(rating.value); + } + if (projectionType.present) { + map['projection_type'] = Variable(projectionType.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES remote_asset_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES remote_album_entity(id)ON DELETE CASCADE', + ); + @override + List get $columns => [assetId, albumId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_asset_entity'; + @override + Set get $primaryKey => {assetId, albumId}; + @override + RemoteAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + ); + } + + @override + RemoteAlbumAssetEntity createAlias(String alias) { + return RemoteAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const [ + 'PRIMARY KEY(asset_id, album_id)', + ]; + @override + bool get dontWriteConstraints => true; +} + +class RemoteAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + const RemoteAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['album_id'] = Variable(albumId); + return map; + } + + factory RemoteAlbumAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + }; + } + + RemoteAlbumAssetEntityData copyWith({String? assetId, String? albumId}) => + RemoteAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + RemoteAlbumAssetEntityData copyWithCompanion( + RemoteAlbumAssetEntityCompanion data, + ) { + return RemoteAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId); +} + +class RemoteAlbumAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value albumId; + const RemoteAlbumAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.albumId = const Value.absent(), + }); + RemoteAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + }) : assetId = Value(assetId), + albumId = Value(albumId); + static Insertable custom({ + Expression? assetId, + Expression? albumId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + }); + } + + RemoteAlbumAssetEntityCompanion copyWith({ + Value? assetId, + Value? albumId, + }) { + return RemoteAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumUserEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumUserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES remote_album_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL REFERENCES user_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn role = GeneratedColumn( + 'role', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + @override + List get $columns => [albumId, userId, role]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_user_entity'; + @override + Set get $primaryKey => {albumId, userId}; + @override + RemoteAlbumUserEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumUserEntityData( + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + role: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}role'], + )!, + ); + } + + @override + RemoteAlbumUserEntity createAlias(String alias) { + return RemoteAlbumUserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const [ + 'PRIMARY KEY(album_id, user_id)', + ]; + @override + bool get dontWriteConstraints => true; +} + +class RemoteAlbumUserEntityData extends DataClass + implements Insertable { + final String albumId; + final String userId; + final int role; + const RemoteAlbumUserEntityData({ + required this.albumId, + required this.userId, + required this.role, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['album_id'] = Variable(albumId); + map['user_id'] = Variable(userId); + map['role'] = Variable(role); + return map; + } + + factory RemoteAlbumUserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumUserEntityData( + albumId: serializer.fromJson(json['albumId']), + userId: serializer.fromJson(json['userId']), + role: serializer.fromJson(json['role']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'albumId': serializer.toJson(albumId), + 'userId': serializer.toJson(userId), + 'role': serializer.toJson(role), + }; + } + + RemoteAlbumUserEntityData copyWith({ + String? albumId, + String? userId, + int? role, + }) => RemoteAlbumUserEntityData( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + RemoteAlbumUserEntityData copyWithCompanion( + RemoteAlbumUserEntityCompanion data, + ) { + return RemoteAlbumUserEntityData( + albumId: data.albumId.present ? data.albumId.value : this.albumId, + userId: data.userId.present ? data.userId.value : this.userId, + role: data.role.present ? data.role.value : this.role, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityData(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(albumId, userId, role); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumUserEntityData && + other.albumId == this.albumId && + other.userId == this.userId && + other.role == this.role); +} + +class RemoteAlbumUserEntityCompanion + extends UpdateCompanion { + final Value albumId; + final Value userId; + final Value role; + const RemoteAlbumUserEntityCompanion({ + this.albumId = const Value.absent(), + this.userId = const Value.absent(), + this.role = const Value.absent(), + }); + RemoteAlbumUserEntityCompanion.insert({ + required String albumId, + required String userId, + required int role, + }) : albumId = Value(albumId), + userId = Value(userId), + role = Value(role); + static Insertable custom({ + Expression? albumId, + Expression? userId, + Expression? role, + }) { + return RawValuesInsertable({ + if (albumId != null) 'album_id': albumId, + if (userId != null) 'user_id': userId, + if (role != null) 'role': role, + }); + } + + RemoteAlbumUserEntityCompanion copyWith({ + Value? albumId, + Value? userId, + Value? role, + }) { + return RemoteAlbumUserEntityCompanion( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (role.present) { + map['role'] = Variable(role.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityCompanion(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } +} + +class RemoteAssetCloudIdEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAssetCloudIdEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES remote_asset_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn cloudId = GeneratedColumn( + 'cloud_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn adjustmentTime = GeneratedColumn( + 'adjustment_time', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + @override + List get $columns => [ + assetId, + cloudId, + createdAt, + adjustmentTime, + latitude, + longitude, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_asset_cloud_id_entity'; + @override + Set get $primaryKey => {assetId}; + @override + RemoteAssetCloudIdEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAssetCloudIdEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + cloudId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}cloud_id'], + ), + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}created_at'], + ), + adjustmentTime: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}adjustment_time'], + ), + latitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + ); + } + + @override + RemoteAssetCloudIdEntity createAlias(String alias) { + return RemoteAssetCloudIdEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(asset_id)']; + @override + bool get dontWriteConstraints => true; +} + +class RemoteAssetCloudIdEntityData extends DataClass + implements Insertable { + final String assetId; + final String? cloudId; + final String? createdAt; + final String? adjustmentTime; + final double? latitude; + final double? longitude; + const RemoteAssetCloudIdEntityData({ + required this.assetId, + this.cloudId, + this.createdAt, + this.adjustmentTime, + this.latitude, + this.longitude, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || cloudId != null) { + map['cloud_id'] = Variable(cloudId); + } + if (!nullToAbsent || createdAt != null) { + map['created_at'] = Variable(createdAt); + } + if (!nullToAbsent || adjustmentTime != null) { + map['adjustment_time'] = Variable(adjustmentTime); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = Variable(longitude); + } + return map; + } + + factory RemoteAssetCloudIdEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAssetCloudIdEntityData( + assetId: serializer.fromJson(json['assetId']), + cloudId: serializer.fromJson(json['cloudId']), + createdAt: serializer.fromJson(json['createdAt']), + adjustmentTime: serializer.fromJson(json['adjustmentTime']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'cloudId': serializer.toJson(cloudId), + 'createdAt': serializer.toJson(createdAt), + 'adjustmentTime': serializer.toJson(adjustmentTime), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + }; + } + + RemoteAssetCloudIdEntityData copyWith({ + String? assetId, + Value cloudId = const Value.absent(), + Value createdAt = const Value.absent(), + Value adjustmentTime = const Value.absent(), + Value latitude = const Value.absent(), + Value longitude = const Value.absent(), + }) => RemoteAssetCloudIdEntityData( + assetId: assetId ?? this.assetId, + cloudId: cloudId.present ? cloudId.value : this.cloudId, + createdAt: createdAt.present ? createdAt.value : this.createdAt, + adjustmentTime: adjustmentTime.present + ? adjustmentTime.value + : this.adjustmentTime, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + ); + RemoteAssetCloudIdEntityData copyWithCompanion( + RemoteAssetCloudIdEntityCompanion data, + ) { + return RemoteAssetCloudIdEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + cloudId: data.cloudId.present ? data.cloudId.value : this.cloudId, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + adjustmentTime: data.adjustmentTime.present + ? data.adjustmentTime.value + : this.adjustmentTime, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAssetCloudIdEntityData(') + ..write('assetId: $assetId, ') + ..write('cloudId: $cloudId, ') + ..write('createdAt: $createdAt, ') + ..write('adjustmentTime: $adjustmentTime, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + assetId, + cloudId, + createdAt, + adjustmentTime, + latitude, + longitude, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAssetCloudIdEntityData && + other.assetId == this.assetId && + other.cloudId == this.cloudId && + other.createdAt == this.createdAt && + other.adjustmentTime == this.adjustmentTime && + other.latitude == this.latitude && + other.longitude == this.longitude); +} + +class RemoteAssetCloudIdEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value cloudId; + final Value createdAt; + final Value adjustmentTime; + final Value latitude; + final Value longitude; + const RemoteAssetCloudIdEntityCompanion({ + this.assetId = const Value.absent(), + this.cloudId = const Value.absent(), + this.createdAt = const Value.absent(), + this.adjustmentTime = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + }); + RemoteAssetCloudIdEntityCompanion.insert({ + required String assetId, + this.cloudId = const Value.absent(), + this.createdAt = const Value.absent(), + this.adjustmentTime = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + }) : assetId = Value(assetId); + static Insertable custom({ + Expression? assetId, + Expression? cloudId, + Expression? createdAt, + Expression? adjustmentTime, + Expression? latitude, + Expression? longitude, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (cloudId != null) 'cloud_id': cloudId, + if (createdAt != null) 'created_at': createdAt, + if (adjustmentTime != null) 'adjustment_time': adjustmentTime, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + }); + } + + RemoteAssetCloudIdEntityCompanion copyWith({ + Value? assetId, + Value? cloudId, + Value? createdAt, + Value? adjustmentTime, + Value? latitude, + Value? longitude, + }) { + return RemoteAssetCloudIdEntityCompanion( + assetId: assetId ?? this.assetId, + cloudId: cloudId ?? this.cloudId, + createdAt: createdAt ?? this.createdAt, + adjustmentTime: adjustmentTime ?? this.adjustmentTime, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (cloudId.present) { + map['cloud_id'] = Variable(cloudId.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (adjustmentTime.present) { + map['adjustment_time'] = Variable(adjustmentTime.value); + } + if (latitude.present) { + map['latitude'] = Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = Variable(longitude.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAssetCloudIdEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('cloudId: $cloudId, ') + ..write('createdAt: $createdAt, ') + ..write('adjustmentTime: $adjustmentTime, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude') + ..write(')')) + .toString(); + } +} + +class MemoryEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MemoryEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL REFERENCES user_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn data = GeneratedColumn( + 'data', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn isSaved = GeneratedColumn( + 'is_saved', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0 CHECK (is_saved IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn memoryAt = GeneratedColumn( + 'memory_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn seenAt = GeneratedColumn( + 'seen_at', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn showAt = GeneratedColumn( + 'show_at', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn hideAt = GeneratedColumn( + 'hide_at', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_entity'; + @override + Set get $primaryKey => {id}; + @override + MemoryEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MemoryEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}deleted_at'], + ), + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + data: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}data'], + )!, + isSaved: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_saved'], + )!, + memoryAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}memory_at'], + )!, + seenAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}seen_at'], + ), + showAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}show_at'], + ), + hideAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}hide_at'], + ), + ); + } + + @override + MemoryEntity createAlias(String alias) { + return MemoryEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class MemoryEntityData extends DataClass + implements Insertable { + final String id; + final String createdAt; + final String updatedAt; + final String? deletedAt; + final String ownerId; + final int type; + final String data; + final int isSaved; + final String memoryAt; + final String? seenAt; + final String? showAt; + final String? hideAt; + const MemoryEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + this.deletedAt, + required this.ownerId, + required this.type, + required this.data, + required this.isSaved, + required this.memoryAt, + this.seenAt, + this.showAt, + this.hideAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + map['owner_id'] = Variable(ownerId); + map['type'] = Variable(type); + map['data'] = Variable(data); + map['is_saved'] = Variable(isSaved); + map['memory_at'] = Variable(memoryAt); + if (!nullToAbsent || seenAt != null) { + map['seen_at'] = Variable(seenAt); + } + if (!nullToAbsent || showAt != null) { + map['show_at'] = Variable(showAt); + } + if (!nullToAbsent || hideAt != null) { + map['hide_at'] = Variable(hideAt); + } + return map; + } + + factory MemoryEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MemoryEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + deletedAt: serializer.fromJson(json['deletedAt']), + ownerId: serializer.fromJson(json['ownerId']), + type: serializer.fromJson(json['type']), + data: serializer.fromJson(json['data']), + isSaved: serializer.fromJson(json['isSaved']), + memoryAt: serializer.fromJson(json['memoryAt']), + seenAt: serializer.fromJson(json['seenAt']), + showAt: serializer.fromJson(json['showAt']), + hideAt: serializer.fromJson(json['hideAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'deletedAt': serializer.toJson(deletedAt), + 'ownerId': serializer.toJson(ownerId), + 'type': serializer.toJson(type), + 'data': serializer.toJson(data), + 'isSaved': serializer.toJson(isSaved), + 'memoryAt': serializer.toJson(memoryAt), + 'seenAt': serializer.toJson(seenAt), + 'showAt': serializer.toJson(showAt), + 'hideAt': serializer.toJson(hideAt), + }; + } + + MemoryEntityData copyWith({ + String? id, + String? createdAt, + String? updatedAt, + Value deletedAt = const Value.absent(), + String? ownerId, + int? type, + String? data, + int? isSaved, + String? memoryAt, + Value seenAt = const Value.absent(), + Value showAt = const Value.absent(), + Value hideAt = const Value.absent(), + }) => MemoryEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt.present ? seenAt.value : this.seenAt, + showAt: showAt.present ? showAt.value : this.showAt, + hideAt: hideAt.present ? hideAt.value : this.hideAt, + ); + MemoryEntityData copyWithCompanion(MemoryEntityCompanion data) { + return MemoryEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + type: data.type.present ? data.type.value : this.type, + data: data.data.present ? data.data.value : this.data, + isSaved: data.isSaved.present ? data.isSaved.value : this.isSaved, + memoryAt: data.memoryAt.present ? data.memoryAt.value : this.memoryAt, + seenAt: data.seenAt.present ? data.seenAt.value : this.seenAt, + showAt: data.showAt.present ? data.showAt.value : this.showAt, + hideAt: data.hideAt.present ? data.hideAt.value : this.hideAt, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MemoryEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.deletedAt == this.deletedAt && + other.ownerId == this.ownerId && + other.type == this.type && + other.data == this.data && + other.isSaved == this.isSaved && + other.memoryAt == this.memoryAt && + other.seenAt == this.seenAt && + other.showAt == this.showAt && + other.hideAt == this.hideAt); +} + +class MemoryEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value deletedAt; + final Value ownerId; + final Value type; + final Value data; + final Value isSaved; + final Value memoryAt; + final Value seenAt; + final Value showAt; + final Value hideAt; + const MemoryEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.deletedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.type = const Value.absent(), + this.data = const Value.absent(), + this.isSaved = const Value.absent(), + this.memoryAt = const Value.absent(), + this.seenAt = const Value.absent(), + this.showAt = const Value.absent(), + this.hideAt = const Value.absent(), + }); + MemoryEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.deletedAt = const Value.absent(), + required String ownerId, + required int type, + required String data, + this.isSaved = const Value.absent(), + required String memoryAt, + this.seenAt = const Value.absent(), + this.showAt = const Value.absent(), + this.hideAt = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + type = Value(type), + data = Value(data), + memoryAt = Value(memoryAt); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? deletedAt, + Expression? ownerId, + Expression? type, + Expression? data, + Expression? isSaved, + Expression? memoryAt, + Expression? seenAt, + Expression? showAt, + Expression? hideAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (deletedAt != null) 'deleted_at': deletedAt, + if (ownerId != null) 'owner_id': ownerId, + if (type != null) 'type': type, + if (data != null) 'data': data, + if (isSaved != null) 'is_saved': isSaved, + if (memoryAt != null) 'memory_at': memoryAt, + if (seenAt != null) 'seen_at': seenAt, + if (showAt != null) 'show_at': showAt, + if (hideAt != null) 'hide_at': hideAt, + }); + } + + MemoryEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? deletedAt, + Value? ownerId, + Value? type, + Value? data, + Value? isSaved, + Value? memoryAt, + Value? seenAt, + Value? showAt, + Value? hideAt, + }) { + return MemoryEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt ?? this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt ?? this.seenAt, + showAt: showAt ?? this.showAt, + hideAt: hideAt ?? this.hideAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (data.present) { + map['data'] = Variable(data.value); + } + if (isSaved.present) { + map['is_saved'] = Variable(isSaved.value); + } + if (memoryAt.present) { + map['memory_at'] = Variable(memoryAt.value); + } + if (seenAt.present) { + map['seen_at'] = Variable(seenAt.value); + } + if (showAt.present) { + map['show_at'] = Variable(showAt.value); + } + if (hideAt.present) { + map['hide_at'] = Variable(hideAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } +} + +class MemoryAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MemoryAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES remote_asset_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn memoryId = GeneratedColumn( + 'memory_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES memory_entity(id)ON DELETE CASCADE', + ); + @override + List get $columns => [assetId, memoryId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_asset_entity'; + @override + Set get $primaryKey => {assetId, memoryId}; + @override + MemoryAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MemoryAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + memoryId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}memory_id'], + )!, + ); + } + + @override + MemoryAssetEntity createAlias(String alias) { + return MemoryAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const [ + 'PRIMARY KEY(asset_id, memory_id)', + ]; + @override + bool get dontWriteConstraints => true; +} + +class MemoryAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String memoryId; + const MemoryAssetEntityData({required this.assetId, required this.memoryId}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['memory_id'] = Variable(memoryId); + return map; + } + + factory MemoryAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MemoryAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + memoryId: serializer.fromJson(json['memoryId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'memoryId': serializer.toJson(memoryId), + }; + } + + MemoryAssetEntityData copyWith({String? assetId, String? memoryId}) => + MemoryAssetEntityData( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + MemoryAssetEntityData copyWithCompanion(MemoryAssetEntityCompanion data) { + return MemoryAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + memoryId: data.memoryId.present ? data.memoryId.value : this.memoryId, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, memoryId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MemoryAssetEntityData && + other.assetId == this.assetId && + other.memoryId == this.memoryId); +} + +class MemoryAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value memoryId; + const MemoryAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.memoryId = const Value.absent(), + }); + MemoryAssetEntityCompanion.insert({ + required String assetId, + required String memoryId, + }) : assetId = Value(assetId), + memoryId = Value(memoryId); + static Insertable custom({ + Expression? assetId, + Expression? memoryId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (memoryId != null) 'memory_id': memoryId, + }); + } + + MemoryAssetEntityCompanion copyWith({ + Value? assetId, + Value? memoryId, + }) { + return MemoryAssetEntityCompanion( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (memoryId.present) { + map['memory_id'] = Variable(memoryId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } +} + +class PersonEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PersonEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL REFERENCES user_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn faceAssetId = GeneratedColumn( + 'face_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL CHECK (is_favorite IN (0, 1))', + ); + late final GeneratedColumn isHidden = GeneratedColumn( + 'is_hidden', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL CHECK (is_hidden IN (0, 1))', + ); + late final GeneratedColumn color = GeneratedColumn( + 'color', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn birthDate = GeneratedColumn( + 'birth_date', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + isFavorite, + isHidden, + color, + birthDate, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'person_entity'; + @override + Set get $primaryKey => {id}; + @override + PersonEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PersonEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + faceAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}face_asset_id'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_favorite'], + )!, + isHidden: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_hidden'], + )!, + color: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}color'], + ), + birthDate: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}birth_date'], + ), + ); + } + + @override + PersonEntity createAlias(String alias) { + return PersonEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class PersonEntityData extends DataClass + implements Insertable { + final String id; + final String createdAt; + final String updatedAt; + final String ownerId; + final String name; + final String? faceAssetId; + final int isFavorite; + final int isHidden; + final String? color; + final String? birthDate; + const PersonEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.name, + this.faceAssetId, + required this.isFavorite, + required this.isHidden, + this.color, + this.birthDate, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + map['name'] = Variable(name); + if (!nullToAbsent || faceAssetId != null) { + map['face_asset_id'] = Variable(faceAssetId); + } + map['is_favorite'] = Variable(isFavorite); + map['is_hidden'] = Variable(isHidden); + if (!nullToAbsent || color != null) { + map['color'] = Variable(color); + } + if (!nullToAbsent || birthDate != null) { + map['birth_date'] = Variable(birthDate); + } + return map; + } + + factory PersonEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PersonEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + name: serializer.fromJson(json['name']), + faceAssetId: serializer.fromJson(json['faceAssetId']), + isFavorite: serializer.fromJson(json['isFavorite']), + isHidden: serializer.fromJson(json['isHidden']), + color: serializer.fromJson(json['color']), + birthDate: serializer.fromJson(json['birthDate']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'name': serializer.toJson(name), + 'faceAssetId': serializer.toJson(faceAssetId), + 'isFavorite': serializer.toJson(isFavorite), + 'isHidden': serializer.toJson(isHidden), + 'color': serializer.toJson(color), + 'birthDate': serializer.toJson(birthDate), + }; + } + + PersonEntityData copyWith({ + String? id, + String? createdAt, + String? updatedAt, + String? ownerId, + String? name, + Value faceAssetId = const Value.absent(), + int? isFavorite, + int? isHidden, + Value color = const Value.absent(), + Value birthDate = const Value.absent(), + }) => PersonEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId.present ? faceAssetId.value : this.faceAssetId, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color.present ? color.value : this.color, + birthDate: birthDate.present ? birthDate.value : this.birthDate, + ); + PersonEntityData copyWithCompanion(PersonEntityCompanion data) { + return PersonEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + name: data.name.present ? data.name.value : this.name, + faceAssetId: data.faceAssetId.present + ? data.faceAssetId.value + : this.faceAssetId, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + isHidden: data.isHidden.present ? data.isHidden.value : this.isHidden, + color: data.color.present ? data.color.value : this.color, + birthDate: data.birthDate.present ? data.birthDate.value : this.birthDate, + ); + } + + @override + String toString() { + return (StringBuffer('PersonEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + isFavorite, + isHidden, + color, + birthDate, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PersonEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.name == this.name && + other.faceAssetId == this.faceAssetId && + other.isFavorite == this.isFavorite && + other.isHidden == this.isHidden && + other.color == this.color && + other.birthDate == this.birthDate); +} + +class PersonEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value name; + final Value faceAssetId; + final Value isFavorite; + final Value isHidden; + final Value color; + final Value birthDate; + const PersonEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.name = const Value.absent(), + this.faceAssetId = const Value.absent(), + this.isFavorite = const Value.absent(), + this.isHidden = const Value.absent(), + this.color = const Value.absent(), + this.birthDate = const Value.absent(), + }); + PersonEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + required String name, + this.faceAssetId = const Value.absent(), + required int isFavorite, + required int isHidden, + this.color = const Value.absent(), + this.birthDate = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + name = Value(name), + isFavorite = Value(isFavorite), + isHidden = Value(isHidden); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? name, + Expression? faceAssetId, + Expression? isFavorite, + Expression? isHidden, + Expression? color, + Expression? birthDate, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (name != null) 'name': name, + if (faceAssetId != null) 'face_asset_id': faceAssetId, + if (isFavorite != null) 'is_favorite': isFavorite, + if (isHidden != null) 'is_hidden': isHidden, + if (color != null) 'color': color, + if (birthDate != null) 'birth_date': birthDate, + }); + } + + PersonEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? name, + Value? faceAssetId, + Value? isFavorite, + Value? isHidden, + Value? color, + Value? birthDate, + }) { + return PersonEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId ?? this.faceAssetId, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color ?? this.color, + birthDate: birthDate ?? this.birthDate, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (faceAssetId.present) { + map['face_asset_id'] = Variable(faceAssetId.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (isHidden.present) { + map['is_hidden'] = Variable(isHidden.value); + } + if (color.present) { + map['color'] = Variable(color.value); + } + if (birthDate.present) { + map['birth_date'] = Variable(birthDate.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PersonEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } +} + +class AssetFaceEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AssetFaceEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES remote_asset_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn personId = GeneratedColumn( + 'person_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL REFERENCES person_entity(id)ON DELETE SET NULL', + ); + late final GeneratedColumn imageWidth = GeneratedColumn( + 'image_width', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn imageHeight = GeneratedColumn( + 'image_height', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn boundingBoxX1 = GeneratedColumn( + 'bounding_box_x1', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn boundingBoxY1 = GeneratedColumn( + 'bounding_box_y1', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn boundingBoxX2 = GeneratedColumn( + 'bounding_box_x2', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn boundingBoxY2 = GeneratedColumn( + 'bounding_box_y2', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn sourceType = GeneratedColumn( + 'source_type', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn isVisible = GeneratedColumn( + 'is_visible', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 1 CHECK (is_visible IN (0, 1))', + defaultValue: const CustomExpression('1'), + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + @override + List get $columns => [ + id, + assetId, + personId, + imageWidth, + imageHeight, + boundingBoxX1, + boundingBoxY1, + boundingBoxX2, + boundingBoxY2, + sourceType, + isVisible, + deletedAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'asset_face_entity'; + @override + Set get $primaryKey => {id}; + @override + AssetFaceEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AssetFaceEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + personId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}person_id'], + ), + imageWidth: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}image_width'], + )!, + imageHeight: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}image_height'], + )!, + boundingBoxX1: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_x1'], + )!, + boundingBoxY1: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_y1'], + )!, + boundingBoxX2: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_x2'], + )!, + boundingBoxY2: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_y2'], + )!, + sourceType: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}source_type'], + )!, + isVisible: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_visible'], + )!, + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}deleted_at'], + ), + ); + } + + @override + AssetFaceEntity createAlias(String alias) { + return AssetFaceEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class AssetFaceEntityData extends DataClass + implements Insertable { + final String id; + final String assetId; + final String? personId; + final int imageWidth; + final int imageHeight; + final int boundingBoxX1; + final int boundingBoxY1; + final int boundingBoxX2; + final int boundingBoxY2; + final String sourceType; + final int isVisible; + final String? deletedAt; + const AssetFaceEntityData({ + required this.id, + required this.assetId, + this.personId, + required this.imageWidth, + required this.imageHeight, + required this.boundingBoxX1, + required this.boundingBoxY1, + required this.boundingBoxX2, + required this.boundingBoxY2, + required this.sourceType, + required this.isVisible, + this.deletedAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || personId != null) { + map['person_id'] = Variable(personId); + } + map['image_width'] = Variable(imageWidth); + map['image_height'] = Variable(imageHeight); + map['bounding_box_x1'] = Variable(boundingBoxX1); + map['bounding_box_y1'] = Variable(boundingBoxY1); + map['bounding_box_x2'] = Variable(boundingBoxX2); + map['bounding_box_y2'] = Variable(boundingBoxY2); + map['source_type'] = Variable(sourceType); + map['is_visible'] = Variable(isVisible); + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + return map; + } + + factory AssetFaceEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AssetFaceEntityData( + id: serializer.fromJson(json['id']), + assetId: serializer.fromJson(json['assetId']), + personId: serializer.fromJson(json['personId']), + imageWidth: serializer.fromJson(json['imageWidth']), + imageHeight: serializer.fromJson(json['imageHeight']), + boundingBoxX1: serializer.fromJson(json['boundingBoxX1']), + boundingBoxY1: serializer.fromJson(json['boundingBoxY1']), + boundingBoxX2: serializer.fromJson(json['boundingBoxX2']), + boundingBoxY2: serializer.fromJson(json['boundingBoxY2']), + sourceType: serializer.fromJson(json['sourceType']), + isVisible: serializer.fromJson(json['isVisible']), + deletedAt: serializer.fromJson(json['deletedAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'assetId': serializer.toJson(assetId), + 'personId': serializer.toJson(personId), + 'imageWidth': serializer.toJson(imageWidth), + 'imageHeight': serializer.toJson(imageHeight), + 'boundingBoxX1': serializer.toJson(boundingBoxX1), + 'boundingBoxY1': serializer.toJson(boundingBoxY1), + 'boundingBoxX2': serializer.toJson(boundingBoxX2), + 'boundingBoxY2': serializer.toJson(boundingBoxY2), + 'sourceType': serializer.toJson(sourceType), + 'isVisible': serializer.toJson(isVisible), + 'deletedAt': serializer.toJson(deletedAt), + }; + } + + AssetFaceEntityData copyWith({ + String? id, + String? assetId, + Value personId = const Value.absent(), + int? imageWidth, + int? imageHeight, + int? boundingBoxX1, + int? boundingBoxY1, + int? boundingBoxX2, + int? boundingBoxY2, + String? sourceType, + int? isVisible, + Value deletedAt = const Value.absent(), + }) => AssetFaceEntityData( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + personId: personId.present ? personId.value : this.personId, + imageWidth: imageWidth ?? this.imageWidth, + imageHeight: imageHeight ?? this.imageHeight, + boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1, + boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1, + boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2, + boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2, + sourceType: sourceType ?? this.sourceType, + isVisible: isVisible ?? this.isVisible, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + ); + AssetFaceEntityData copyWithCompanion(AssetFaceEntityCompanion data) { + return AssetFaceEntityData( + id: data.id.present ? data.id.value : this.id, + assetId: data.assetId.present ? data.assetId.value : this.assetId, + personId: data.personId.present ? data.personId.value : this.personId, + imageWidth: data.imageWidth.present + ? data.imageWidth.value + : this.imageWidth, + imageHeight: data.imageHeight.present + ? data.imageHeight.value + : this.imageHeight, + boundingBoxX1: data.boundingBoxX1.present + ? data.boundingBoxX1.value + : this.boundingBoxX1, + boundingBoxY1: data.boundingBoxY1.present + ? data.boundingBoxY1.value + : this.boundingBoxY1, + boundingBoxX2: data.boundingBoxX2.present + ? data.boundingBoxX2.value + : this.boundingBoxX2, + boundingBoxY2: data.boundingBoxY2.present + ? data.boundingBoxY2.value + : this.boundingBoxY2, + sourceType: data.sourceType.present + ? data.sourceType.value + : this.sourceType, + isVisible: data.isVisible.present ? data.isVisible.value : this.isVisible, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + ); + } + + @override + String toString() { + return (StringBuffer('AssetFaceEntityData(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('personId: $personId, ') + ..write('imageWidth: $imageWidth, ') + ..write('imageHeight: $imageHeight, ') + ..write('boundingBoxX1: $boundingBoxX1, ') + ..write('boundingBoxY1: $boundingBoxY1, ') + ..write('boundingBoxX2: $boundingBoxX2, ') + ..write('boundingBoxY2: $boundingBoxY2, ') + ..write('sourceType: $sourceType, ') + ..write('isVisible: $isVisible, ') + ..write('deletedAt: $deletedAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + assetId, + personId, + imageWidth, + imageHeight, + boundingBoxX1, + boundingBoxY1, + boundingBoxX2, + boundingBoxY2, + sourceType, + isVisible, + deletedAt, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AssetFaceEntityData && + other.id == this.id && + other.assetId == this.assetId && + other.personId == this.personId && + other.imageWidth == this.imageWidth && + other.imageHeight == this.imageHeight && + other.boundingBoxX1 == this.boundingBoxX1 && + other.boundingBoxY1 == this.boundingBoxY1 && + other.boundingBoxX2 == this.boundingBoxX2 && + other.boundingBoxY2 == this.boundingBoxY2 && + other.sourceType == this.sourceType && + other.isVisible == this.isVisible && + other.deletedAt == this.deletedAt); +} + +class AssetFaceEntityCompanion extends UpdateCompanion { + final Value id; + final Value assetId; + final Value personId; + final Value imageWidth; + final Value imageHeight; + final Value boundingBoxX1; + final Value boundingBoxY1; + final Value boundingBoxX2; + final Value boundingBoxY2; + final Value sourceType; + final Value isVisible; + final Value deletedAt; + const AssetFaceEntityCompanion({ + this.id = const Value.absent(), + this.assetId = const Value.absent(), + this.personId = const Value.absent(), + this.imageWidth = const Value.absent(), + this.imageHeight = const Value.absent(), + this.boundingBoxX1 = const Value.absent(), + this.boundingBoxY1 = const Value.absent(), + this.boundingBoxX2 = const Value.absent(), + this.boundingBoxY2 = const Value.absent(), + this.sourceType = const Value.absent(), + this.isVisible = const Value.absent(), + this.deletedAt = const Value.absent(), + }); + AssetFaceEntityCompanion.insert({ + required String id, + required String assetId, + this.personId = const Value.absent(), + required int imageWidth, + required int imageHeight, + required int boundingBoxX1, + required int boundingBoxY1, + required int boundingBoxX2, + required int boundingBoxY2, + required String sourceType, + this.isVisible = const Value.absent(), + this.deletedAt = const Value.absent(), + }) : id = Value(id), + assetId = Value(assetId), + imageWidth = Value(imageWidth), + imageHeight = Value(imageHeight), + boundingBoxX1 = Value(boundingBoxX1), + boundingBoxY1 = Value(boundingBoxY1), + boundingBoxX2 = Value(boundingBoxX2), + boundingBoxY2 = Value(boundingBoxY2), + sourceType = Value(sourceType); + static Insertable custom({ + Expression? id, + Expression? assetId, + Expression? personId, + Expression? imageWidth, + Expression? imageHeight, + Expression? boundingBoxX1, + Expression? boundingBoxY1, + Expression? boundingBoxX2, + Expression? boundingBoxY2, + Expression? sourceType, + Expression? isVisible, + Expression? deletedAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (assetId != null) 'asset_id': assetId, + if (personId != null) 'person_id': personId, + if (imageWidth != null) 'image_width': imageWidth, + if (imageHeight != null) 'image_height': imageHeight, + if (boundingBoxX1 != null) 'bounding_box_x1': boundingBoxX1, + if (boundingBoxY1 != null) 'bounding_box_y1': boundingBoxY1, + if (boundingBoxX2 != null) 'bounding_box_x2': boundingBoxX2, + if (boundingBoxY2 != null) 'bounding_box_y2': boundingBoxY2, + if (sourceType != null) 'source_type': sourceType, + if (isVisible != null) 'is_visible': isVisible, + if (deletedAt != null) 'deleted_at': deletedAt, + }); + } + + AssetFaceEntityCompanion copyWith({ + Value? id, + Value? assetId, + Value? personId, + Value? imageWidth, + Value? imageHeight, + Value? boundingBoxX1, + Value? boundingBoxY1, + Value? boundingBoxX2, + Value? boundingBoxY2, + Value? sourceType, + Value? isVisible, + Value? deletedAt, + }) { + return AssetFaceEntityCompanion( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + personId: personId ?? this.personId, + imageWidth: imageWidth ?? this.imageWidth, + imageHeight: imageHeight ?? this.imageHeight, + boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1, + boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1, + boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2, + boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2, + sourceType: sourceType ?? this.sourceType, + isVisible: isVisible ?? this.isVisible, + deletedAt: deletedAt ?? this.deletedAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (personId.present) { + map['person_id'] = Variable(personId.value); + } + if (imageWidth.present) { + map['image_width'] = Variable(imageWidth.value); + } + if (imageHeight.present) { + map['image_height'] = Variable(imageHeight.value); + } + if (boundingBoxX1.present) { + map['bounding_box_x1'] = Variable(boundingBoxX1.value); + } + if (boundingBoxY1.present) { + map['bounding_box_y1'] = Variable(boundingBoxY1.value); + } + if (boundingBoxX2.present) { + map['bounding_box_x2'] = Variable(boundingBoxX2.value); + } + if (boundingBoxY2.present) { + map['bounding_box_y2'] = Variable(boundingBoxY2.value); + } + if (sourceType.present) { + map['source_type'] = Variable(sourceType.value); + } + if (isVisible.present) { + map['is_visible'] = Variable(isVisible.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AssetFaceEntityCompanion(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('personId: $personId, ') + ..write('imageWidth: $imageWidth, ') + ..write('imageHeight: $imageHeight, ') + ..write('boundingBoxX1: $boundingBoxX1, ') + ..write('boundingBoxY1: $boundingBoxY1, ') + ..write('boundingBoxX2: $boundingBoxX2, ') + ..write('boundingBoxY2: $boundingBoxY2, ') + ..write('sourceType: $sourceType, ') + ..write('isVisible: $isVisible, ') + ..write('deletedAt: $deletedAt') + ..write(')')) + .toString(); + } +} + +class StoreEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + StoreEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn stringValue = GeneratedColumn( + 'string_value', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn intValue = GeneratedColumn( + 'int_value', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + @override + List get $columns => [id, stringValue, intValue]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'store_entity'; + @override + Set get $primaryKey => {id}; + @override + StoreEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return StoreEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}id'], + )!, + stringValue: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}string_value'], + ), + intValue: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}int_value'], + ), + ); + } + + @override + StoreEntity createAlias(String alias) { + return StoreEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class StoreEntityData extends DataClass implements Insertable { + final int id; + final String? stringValue; + final int? intValue; + const StoreEntityData({required this.id, this.stringValue, this.intValue}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + if (!nullToAbsent || stringValue != null) { + map['string_value'] = Variable(stringValue); + } + if (!nullToAbsent || intValue != null) { + map['int_value'] = Variable(intValue); + } + return map; + } + + factory StoreEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return StoreEntityData( + id: serializer.fromJson(json['id']), + stringValue: serializer.fromJson(json['stringValue']), + intValue: serializer.fromJson(json['intValue']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'stringValue': serializer.toJson(stringValue), + 'intValue': serializer.toJson(intValue), + }; + } + + StoreEntityData copyWith({ + int? id, + Value stringValue = const Value.absent(), + Value intValue = const Value.absent(), + }) => StoreEntityData( + id: id ?? this.id, + stringValue: stringValue.present ? stringValue.value : this.stringValue, + intValue: intValue.present ? intValue.value : this.intValue, + ); + StoreEntityData copyWithCompanion(StoreEntityCompanion data) { + return StoreEntityData( + id: data.id.present ? data.id.value : this.id, + stringValue: data.stringValue.present + ? data.stringValue.value + : this.stringValue, + intValue: data.intValue.present ? data.intValue.value : this.intValue, + ); + } + + @override + String toString() { + return (StringBuffer('StoreEntityData(') + ..write('id: $id, ') + ..write('stringValue: $stringValue, ') + ..write('intValue: $intValue') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, stringValue, intValue); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is StoreEntityData && + other.id == this.id && + other.stringValue == this.stringValue && + other.intValue == this.intValue); +} + +class StoreEntityCompanion extends UpdateCompanion { + final Value id; + final Value stringValue; + final Value intValue; + const StoreEntityCompanion({ + this.id = const Value.absent(), + this.stringValue = const Value.absent(), + this.intValue = const Value.absent(), + }); + StoreEntityCompanion.insert({ + required int id, + this.stringValue = const Value.absent(), + this.intValue = const Value.absent(), + }) : id = Value(id); + static Insertable custom({ + Expression? id, + Expression? stringValue, + Expression? intValue, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (stringValue != null) 'string_value': stringValue, + if (intValue != null) 'int_value': intValue, + }); + } + + StoreEntityCompanion copyWith({ + Value? id, + Value? stringValue, + Value? intValue, + }) { + return StoreEntityCompanion( + id: id ?? this.id, + stringValue: stringValue ?? this.stringValue, + intValue: intValue ?? this.intValue, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (stringValue.present) { + map['string_value'] = Variable(stringValue.value); + } + if (intValue.present) { + map['int_value'] = Variable(intValue.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('StoreEntityCompanion(') + ..write('id: $id, ') + ..write('stringValue: $stringValue, ') + ..write('intValue: $intValue') + ..write(')')) + .toString(); + } +} + +class TrashedLocalAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + TrashedLocalAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn durationMs = GeneratedColumn( + 'duration_ms', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0 CHECK (is_favorite IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn source = GeneratedColumn( + 'source', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn playbackStyle = GeneratedColumn( + 'playback_style', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0', + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationMs, + id, + albumId, + checksum, + isFavorite, + orientation, + source, + playbackStyle, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'trashed_local_asset_entity'; + @override + Set get $primaryKey => {id, albumId}; + @override + TrashedLocalAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return TrashedLocalAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationMs: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_ms'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_favorite'], + )!, + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}orientation'], + )!, + source: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}source'], + )!, + playbackStyle: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}playback_style'], + )!, + ); + } + + @override + TrashedLocalAssetEntity createAlias(String alias) { + return TrashedLocalAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id, album_id)']; + @override + bool get dontWriteConstraints => true; +} + +class TrashedLocalAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final String createdAt; + final String updatedAt; + final int? width; + final int? height; + final int? durationMs; + final String id; + final String albumId; + final String? checksum; + final int isFavorite; + final int orientation; + final int source; + final int playbackStyle; + const TrashedLocalAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationMs, + required this.id, + required this.albumId, + this.checksum, + required this.isFavorite, + required this.orientation, + required this.source, + required this.playbackStyle, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationMs != null) { + map['duration_ms'] = Variable(durationMs); + } + map['id'] = Variable(id); + map['album_id'] = Variable(albumId); + if (!nullToAbsent || checksum != null) { + map['checksum'] = Variable(checksum); + } + map['is_favorite'] = Variable(isFavorite); + map['orientation'] = Variable(orientation); + map['source'] = Variable(source); + map['playback_style'] = Variable(playbackStyle); + return map; + } + + factory TrashedLocalAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return TrashedLocalAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationMs: serializer.fromJson(json['durationMs']), + id: serializer.fromJson(json['id']), + albumId: serializer.fromJson(json['albumId']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + orientation: serializer.fromJson(json['orientation']), + source: serializer.fromJson(json['source']), + playbackStyle: serializer.fromJson(json['playbackStyle']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationMs': serializer.toJson(durationMs), + 'id': serializer.toJson(id), + 'albumId': serializer.toJson(albumId), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'orientation': serializer.toJson(orientation), + 'source': serializer.toJson(source), + 'playbackStyle': serializer.toJson(playbackStyle), + }; + } + + TrashedLocalAssetEntityData copyWith({ + String? name, + int? type, + String? createdAt, + String? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationMs = const Value.absent(), + String? id, + String? albumId, + Value checksum = const Value.absent(), + int? isFavorite, + int? orientation, + int? source, + int? playbackStyle, + }) => TrashedLocalAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationMs: durationMs.present ? durationMs.value : this.durationMs, + id: id ?? this.id, + albumId: albumId ?? this.albumId, + checksum: checksum.present ? checksum.value : this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + source: source ?? this.source, + playbackStyle: playbackStyle ?? this.playbackStyle, + ); + TrashedLocalAssetEntityData copyWithCompanion( + TrashedLocalAssetEntityCompanion data, + ) { + return TrashedLocalAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationMs: data.durationMs.present + ? data.durationMs.value + : this.durationMs, + id: data.id.present ? data.id.value : this.id, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + source: data.source.present ? data.source.value : this.source, + playbackStyle: data.playbackStyle.present + ? data.playbackStyle.value + : this.playbackStyle, + ); + } + + @override + String toString() { + return (StringBuffer('TrashedLocalAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationMs: $durationMs, ') + ..write('id: $id, ') + ..write('albumId: $albumId, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation, ') + ..write('source: $source, ') + ..write('playbackStyle: $playbackStyle') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationMs, + id, + albumId, + checksum, + isFavorite, + orientation, + source, + playbackStyle, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is TrashedLocalAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationMs == this.durationMs && + other.id == this.id && + other.albumId == this.albumId && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.orientation == this.orientation && + other.source == this.source && + other.playbackStyle == this.playbackStyle); +} + +class TrashedLocalAssetEntityCompanion + extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationMs; + final Value id; + final Value albumId; + final Value checksum; + final Value isFavorite; + final Value orientation; + final Value source; + final Value playbackStyle; + const TrashedLocalAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationMs = const Value.absent(), + this.id = const Value.absent(), + this.albumId = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + this.source = const Value.absent(), + this.playbackStyle = const Value.absent(), + }); + TrashedLocalAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationMs = const Value.absent(), + required String id, + required String albumId, + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + required int source, + this.playbackStyle = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id), + albumId = Value(albumId), + source = Value(source); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationMs, + Expression? id, + Expression? albumId, + Expression? checksum, + Expression? isFavorite, + Expression? orientation, + Expression? source, + Expression? playbackStyle, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationMs != null) 'duration_ms': durationMs, + if (id != null) 'id': id, + if (albumId != null) 'album_id': albumId, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (orientation != null) 'orientation': orientation, + if (source != null) 'source': source, + if (playbackStyle != null) 'playback_style': playbackStyle, + }); + } + + TrashedLocalAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationMs, + Value? id, + Value? albumId, + Value? checksum, + Value? isFavorite, + Value? orientation, + Value? source, + Value? playbackStyle, + }) { + return TrashedLocalAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationMs: durationMs ?? this.durationMs, + id: id ?? this.id, + albumId: albumId ?? this.albumId, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + source: source ?? this.source, + playbackStyle: playbackStyle ?? this.playbackStyle, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationMs.present) { + map['duration_ms'] = Variable(durationMs.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + if (source.present) { + map['source'] = Variable(source.value); + } + if (playbackStyle.present) { + map['playback_style'] = Variable(playbackStyle.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('TrashedLocalAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationMs: $durationMs, ') + ..write('id: $id, ') + ..write('albumId: $albumId, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation, ') + ..write('source: $source, ') + ..write('playbackStyle: $playbackStyle') + ..write(')')) + .toString(); + } +} + +class AssetEditEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AssetEditEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES remote_asset_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn action = GeneratedColumn( + 'action', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn parameters = + GeneratedColumn( + 'parameters', + aliasedName, + false, + type: DriftSqlType.blob, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn sequence = GeneratedColumn( + 'sequence', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + @override + List get $columns => [ + id, + assetId, + action, + parameters, + sequence, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'asset_edit_entity'; + @override + Set get $primaryKey => {id}; + @override + AssetEditEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AssetEditEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + action: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}action'], + )!, + parameters: attachedDatabase.typeMapping.read( + DriftSqlType.blob, + data['${effectivePrefix}parameters'], + )!, + sequence: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}sequence'], + )!, + ); + } + + @override + AssetEditEntity createAlias(String alias) { + return AssetEditEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class AssetEditEntityData extends DataClass + implements Insertable { + final String id; + final String assetId; + final int action; + final i2.Uint8List parameters; + final int sequence; + const AssetEditEntityData({ + required this.id, + required this.assetId, + required this.action, + required this.parameters, + required this.sequence, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['asset_id'] = Variable(assetId); + map['action'] = Variable(action); + map['parameters'] = Variable(parameters); + map['sequence'] = Variable(sequence); + return map; + } + + factory AssetEditEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AssetEditEntityData( + id: serializer.fromJson(json['id']), + assetId: serializer.fromJson(json['assetId']), + action: serializer.fromJson(json['action']), + parameters: serializer.fromJson(json['parameters']), + sequence: serializer.fromJson(json['sequence']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'assetId': serializer.toJson(assetId), + 'action': serializer.toJson(action), + 'parameters': serializer.toJson(parameters), + 'sequence': serializer.toJson(sequence), + }; + } + + AssetEditEntityData copyWith({ + String? id, + String? assetId, + int? action, + i2.Uint8List? parameters, + int? sequence, + }) => AssetEditEntityData( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + action: action ?? this.action, + parameters: parameters ?? this.parameters, + sequence: sequence ?? this.sequence, + ); + AssetEditEntityData copyWithCompanion(AssetEditEntityCompanion data) { + return AssetEditEntityData( + id: data.id.present ? data.id.value : this.id, + assetId: data.assetId.present ? data.assetId.value : this.assetId, + action: data.action.present ? data.action.value : this.action, + parameters: data.parameters.present + ? data.parameters.value + : this.parameters, + sequence: data.sequence.present ? data.sequence.value : this.sequence, + ); + } + + @override + String toString() { + return (StringBuffer('AssetEditEntityData(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('action: $action, ') + ..write('parameters: $parameters, ') + ..write('sequence: $sequence') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + assetId, + action, + $driftBlobEquality.hash(parameters), + sequence, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AssetEditEntityData && + other.id == this.id && + other.assetId == this.assetId && + other.action == this.action && + $driftBlobEquality.equals(other.parameters, this.parameters) && + other.sequence == this.sequence); +} + +class AssetEditEntityCompanion extends UpdateCompanion { + final Value id; + final Value assetId; + final Value action; + final Value parameters; + final Value sequence; + const AssetEditEntityCompanion({ + this.id = const Value.absent(), + this.assetId = const Value.absent(), + this.action = const Value.absent(), + this.parameters = const Value.absent(), + this.sequence = const Value.absent(), + }); + AssetEditEntityCompanion.insert({ + required String id, + required String assetId, + required int action, + required i2.Uint8List parameters, + required int sequence, + }) : id = Value(id), + assetId = Value(assetId), + action = Value(action), + parameters = Value(parameters), + sequence = Value(sequence); + static Insertable custom({ + Expression? id, + Expression? assetId, + Expression? action, + Expression? parameters, + Expression? sequence, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (assetId != null) 'asset_id': assetId, + if (action != null) 'action': action, + if (parameters != null) 'parameters': parameters, + if (sequence != null) 'sequence': sequence, + }); + } + + AssetEditEntityCompanion copyWith({ + Value? id, + Value? assetId, + Value? action, + Value? parameters, + Value? sequence, + }) { + return AssetEditEntityCompanion( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + action: action ?? this.action, + parameters: parameters ?? this.parameters, + sequence: sequence ?? this.sequence, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (action.present) { + map['action'] = Variable(action.value); + } + if (parameters.present) { + map['parameters'] = Variable(parameters.value); + } + if (sequence.present) { + map['sequence'] = Variable(sequence.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AssetEditEntityCompanion(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('action: $action, ') + ..write('parameters: $parameters, ') + ..write('sequence: $sequence') + ..write(')')) + .toString(); + } +} + +class Settings extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + Settings(this.attachedDatabase, [this._alias]); + late final GeneratedColumn key = GeneratedColumn( + 'key', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn value = GeneratedColumn( + 'value', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + @override + List get $columns => [key, value, updatedAt]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'settings'; + @override + Set get $primaryKey => {key}; + @override + SettingsData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return SettingsData( + key: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}key'], + )!, + value: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}value'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + ); + } + + @override + Settings createAlias(String alias) { + return Settings(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY("key")']; + @override + bool get dontWriteConstraints => true; +} + +class SettingsData extends DataClass implements Insertable { + final String key; + final String value; + final String updatedAt; + const SettingsData({ + required this.key, + required this.value, + required this.updatedAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['key'] = Variable(key); + map['value'] = Variable(value); + map['updated_at'] = Variable(updatedAt); + return map; + } + + factory SettingsData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return SettingsData( + key: serializer.fromJson(json['key']), + value: serializer.fromJson(json['value']), + updatedAt: serializer.fromJson(json['updatedAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'key': serializer.toJson(key), + 'value': serializer.toJson(value), + 'updatedAt': serializer.toJson(updatedAt), + }; + } + + SettingsData copyWith({String? key, String? value, String? updatedAt}) => + SettingsData( + key: key ?? this.key, + value: value ?? this.value, + updatedAt: updatedAt ?? this.updatedAt, + ); + SettingsData copyWithCompanion(SettingsCompanion data) { + return SettingsData( + key: data.key.present ? data.key.value : this.key, + value: data.value.present ? data.value.value : this.value, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ); + } + + @override + String toString() { + return (StringBuffer('SettingsData(') + ..write('key: $key, ') + ..write('value: $value, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(key, value, updatedAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is SettingsData && + other.key == this.key && + other.value == this.value && + other.updatedAt == this.updatedAt); +} + +class SettingsCompanion extends UpdateCompanion { + final Value key; + final Value value; + final Value updatedAt; + const SettingsCompanion({ + this.key = const Value.absent(), + this.value = const Value.absent(), + this.updatedAt = const Value.absent(), + }); + SettingsCompanion.insert({ + required String key, + required String value, + this.updatedAt = const Value.absent(), + }) : key = Value(key), + value = Value(value); + static Insertable custom({ + Expression? key, + Expression? value, + Expression? updatedAt, + }) { + return RawValuesInsertable({ + if (key != null) 'key': key, + if (value != null) 'value': value, + if (updatedAt != null) 'updated_at': updatedAt, + }); + } + + SettingsCompanion copyWith({ + Value? key, + Value? value, + Value? updatedAt, + }) { + return SettingsCompanion( + key: key ?? this.key, + value: value ?? this.value, + updatedAt: updatedAt ?? this.updatedAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (key.present) { + map['key'] = Variable(key.value); + } + if (value.present) { + map['value'] = Variable(value.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('SettingsCompanion(') + ..write('key: $key, ') + ..write('value: $value, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } +} + +class DatabaseAtV27 extends GeneratedDatabase { + DatabaseAtV27(QueryExecutor e) : super(e); + late final UserEntity userEntity = UserEntity(this); + late final RemoteAssetEntity remoteAssetEntity = RemoteAssetEntity(this); + late final StackEntity stackEntity = StackEntity(this); + late final LocalAssetEntity localAssetEntity = LocalAssetEntity(this); + late final RemoteAlbumEntity remoteAlbumEntity = RemoteAlbumEntity(this); + late final LocalAlbumEntity localAlbumEntity = LocalAlbumEntity(this); + late final LocalAlbumAssetEntity localAlbumAssetEntity = + LocalAlbumAssetEntity(this); + late final Index idxLocalAlbumAssetAlbumAsset = Index( + 'idx_local_album_asset_album_asset', + 'CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)', + ); + late final Index idxLocalAssetChecksum = Index( + 'idx_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)', + ); + late final Index idxLocalAssetCloudId = Index( + 'idx_local_asset_cloud_id', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)', + ); + late final Index idxStackPrimaryAssetId = Index( + 'idx_stack_primary_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)', + ); + late final Index uQRemoteAssetsOwnerChecksum = Index( + 'UQ_remote_assets_owner_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)', + ); + late final Index uQRemoteAssetsOwnerLibraryChecksum = Index( + 'UQ_remote_assets_owner_library_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)', + ); + late final Index idxRemoteAssetChecksum = Index( + 'idx_remote_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)', + ); + late final Index idxRemoteAssetStackId = Index( + 'idx_remote_asset_stack_id', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)', + ); + late final Index idxRemoteAssetOwnerVisibilityDeletedCreated = Index( + 'idx_remote_asset_owner_visibility_deleted_created', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_visibility_deleted_created ON remote_asset_entity (owner_id, visibility, deleted_at, created_at DESC)', + ); + late final AuthUserEntity authUserEntity = AuthUserEntity(this); + late final UserMetadataEntity userMetadataEntity = UserMetadataEntity(this); + late final PartnerEntity partnerEntity = PartnerEntity(this); + late final RemoteExifEntity remoteExifEntity = RemoteExifEntity(this); + late final RemoteAlbumAssetEntity remoteAlbumAssetEntity = + RemoteAlbumAssetEntity(this); + late final RemoteAlbumUserEntity remoteAlbumUserEntity = + RemoteAlbumUserEntity(this); + late final RemoteAssetCloudIdEntity remoteAssetCloudIdEntity = + RemoteAssetCloudIdEntity(this); + late final MemoryEntity memoryEntity = MemoryEntity(this); + late final MemoryAssetEntity memoryAssetEntity = MemoryAssetEntity(this); + late final PersonEntity personEntity = PersonEntity(this); + late final AssetFaceEntity assetFaceEntity = AssetFaceEntity(this); + late final StoreEntity storeEntity = StoreEntity(this); + late final TrashedLocalAssetEntity trashedLocalAssetEntity = + TrashedLocalAssetEntity(this); + late final AssetEditEntity assetEditEntity = AssetEditEntity(this); + late final Settings settings = Settings(this); + late final Index idxPartnerSharedWithId = Index( + 'idx_partner_shared_with_id', + 'CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)', + ); + late final Index idxLatLng = Index( + 'idx_lat_lng', + 'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)', + ); + late final Index idxRemoteExifCity = Index( + 'idx_remote_exif_city', + 'CREATE INDEX IF NOT EXISTS idx_remote_exif_city ON remote_exif_entity (city) WHERE city IS NOT NULL', + ); + late final Index idxRemoteAlbumAssetAlbumAsset = Index( + 'idx_remote_album_asset_album_asset', + 'CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)', + ); + late final Index idxRemoteAssetCloudId = Index( + 'idx_remote_asset_cloud_id', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)', + ); + late final Index idxPersonOwnerId = Index( + 'idx_person_owner_id', + 'CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)', + ); + late final Index idxAssetFacePersonId = Index( + 'idx_asset_face_person_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)', + ); + late final Index idxAssetFaceAssetId = Index( + 'idx_asset_face_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)', + ); + late final Index idxAssetFaceVisiblePerson = Index( + 'idx_asset_face_visible_person', + 'CREATE INDEX IF NOT EXISTS idx_asset_face_visible_person ON asset_face_entity (person_id, asset_id) WHERE is_visible = 1 AND deleted_at IS NULL', + ); + late final Index idxTrashedLocalAssetChecksum = Index( + 'idx_trashed_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)', + ); + late final Index idxTrashedLocalAssetAlbum = Index( + 'idx_trashed_local_asset_album', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)', + ); + late final Index idxAssetEditAssetId = Index( + 'idx_asset_edit_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_edit_asset_id ON asset_edit_entity (asset_id)', + ); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [ + userEntity, + remoteAssetEntity, + stackEntity, + localAssetEntity, + remoteAlbumEntity, + localAlbumEntity, + localAlbumAssetEntity, + idxLocalAlbumAssetAlbumAsset, + idxLocalAssetChecksum, + idxLocalAssetCloudId, + idxStackPrimaryAssetId, + uQRemoteAssetsOwnerChecksum, + uQRemoteAssetsOwnerLibraryChecksum, + idxRemoteAssetChecksum, + idxRemoteAssetStackId, + idxRemoteAssetOwnerVisibilityDeletedCreated, + authUserEntity, + userMetadataEntity, + partnerEntity, + remoteExifEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + remoteAssetCloudIdEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + assetFaceEntity, + storeEntity, + trashedLocalAssetEntity, + assetEditEntity, + settings, + idxPartnerSharedWithId, + idxLatLng, + idxRemoteExifCity, + idxRemoteAlbumAssetAlbumAsset, + idxRemoteAssetCloudId, + idxPersonOwnerId, + idxAssetFacePersonId, + idxAssetFaceAssetId, + idxAssetFaceVisiblePerson, + idxTrashedLocalAssetChecksum, + idxTrashedLocalAssetAlbum, + idxAssetEditAssetId, + ]; + @override + StreamQueryUpdateRules get streamUpdateRules => const StreamQueryUpdateRules([ + WritePropagation( + on: TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('remote_asset_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('stack_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_asset_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('remote_album_entity', kind: UpdateKind.update)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_album_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('local_album_entity', kind: UpdateKind.update)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'local_asset_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [ + TableUpdate('local_album_asset_entity', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'local_album_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [ + TableUpdate('local_album_asset_entity', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('user_metadata_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('partner_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('partner_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_asset_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('remote_exif_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_asset_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [ + TableUpdate('remote_album_asset_entity', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_album_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [ + TableUpdate('remote_album_asset_entity', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_album_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [ + TableUpdate('remote_album_user_entity', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [ + TableUpdate('remote_album_user_entity', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_asset_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [ + TableUpdate('remote_asset_cloud_id_entity', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('memory_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_asset_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('memory_asset_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'memory_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('memory_asset_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('person_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_asset_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('asset_face_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'person_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('asset_face_entity', kind: UpdateKind.update)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_asset_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('asset_edit_entity', kind: UpdateKind.delete)], + ), + ]); + @override + int get schemaVersion => 27; + @override + DriftDatabaseOptions get options => + const DriftDatabaseOptions(storeDateTimeAsText: true); +} diff --git a/mobile/test/drift/main/generated/schema_v28.dart b/mobile/test/drift/main/generated/schema_v28.dart new file mode 100644 index 0000000000..a18199ed4f --- /dev/null +++ b/mobile/test/drift/main/generated/schema_v28.dart @@ -0,0 +1,9389 @@ +// dart format width=80 +import 'dart:typed_data' as i2; +// GENERATED BY drift_dev, DO NOT MODIFY. +// ignore_for_file: type=lint,unused_import +// +import 'package:drift/drift.dart'; + +class UserEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn email = GeneratedColumn( + 'email', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn hasProfileImage = GeneratedColumn( + 'has_profile_image', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: + 'NOT NULL DEFAULT 0 CHECK (has_profile_image IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn profileChangedAt = GeneratedColumn( + 'profile_changed_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn avatarColor = GeneratedColumn( + 'avatar_color', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0', + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + id, + name, + email, + hasProfileImage, + profileChangedAt, + avatarColor, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_entity'; + @override + Set get $primaryKey => {id}; + @override + UserEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + email: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}email'], + )!, + hasProfileImage: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}has_profile_image'], + )!, + profileChangedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}profile_changed_at'], + )!, + avatarColor: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}avatar_color'], + )!, + ); + } + + @override + UserEntity createAlias(String alias) { + return UserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class UserEntityData extends DataClass implements Insertable { + final String id; + final String name; + final String email; + final int hasProfileImage; + final String profileChangedAt; + final int avatarColor; + const UserEntityData({ + required this.id, + required this.name, + required this.email, + required this.hasProfileImage, + required this.profileChangedAt, + required this.avatarColor, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['email'] = Variable(email); + map['has_profile_image'] = Variable(hasProfileImage); + map['profile_changed_at'] = Variable(profileChangedAt); + map['avatar_color'] = Variable(avatarColor); + return map; + } + + factory UserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + email: serializer.fromJson(json['email']), + hasProfileImage: serializer.fromJson(json['hasProfileImage']), + profileChangedAt: serializer.fromJson(json['profileChangedAt']), + avatarColor: serializer.fromJson(json['avatarColor']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'email': serializer.toJson(email), + 'hasProfileImage': serializer.toJson(hasProfileImage), + 'profileChangedAt': serializer.toJson(profileChangedAt), + 'avatarColor': serializer.toJson(avatarColor), + }; + } + + UserEntityData copyWith({ + String? id, + String? name, + String? email, + int? hasProfileImage, + String? profileChangedAt, + int? avatarColor, + }) => UserEntityData( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + ); + UserEntityData copyWithCompanion(UserEntityCompanion data) { + return UserEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + email: data.email.present ? data.email.value : this.email, + hasProfileImage: data.hasProfileImage.present + ? data.hasProfileImage.value + : this.hasProfileImage, + profileChangedAt: data.profileChangedAt.present + ? data.profileChangedAt.value + : this.profileChangedAt, + avatarColor: data.avatarColor.present + ? data.avatarColor.value + : this.avatarColor, + ); + } + + @override + String toString() { + return (StringBuffer('UserEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + email, + hasProfileImage, + profileChangedAt, + avatarColor, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserEntityData && + other.id == this.id && + other.name == this.name && + other.email == this.email && + other.hasProfileImage == this.hasProfileImage && + other.profileChangedAt == this.profileChangedAt && + other.avatarColor == this.avatarColor); +} + +class UserEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value email; + final Value hasProfileImage; + final Value profileChangedAt; + final Value avatarColor; + const UserEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.email = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + }); + UserEntityCompanion.insert({ + required String id, + required String name, + required String email, + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + }) : id = Value(id), + name = Value(name), + email = Value(email); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? email, + Expression? hasProfileImage, + Expression? profileChangedAt, + Expression? avatarColor, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (email != null) 'email': email, + if (hasProfileImage != null) 'has_profile_image': hasProfileImage, + if (profileChangedAt != null) 'profile_changed_at': profileChangedAt, + if (avatarColor != null) 'avatar_color': avatarColor, + }); + } + + UserEntityCompanion copyWith({ + Value? id, + Value? name, + Value? email, + Value? hasProfileImage, + Value? profileChangedAt, + Value? avatarColor, + }) { + return UserEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (email.present) { + map['email'] = Variable(email.value); + } + if (hasProfileImage.present) { + map['has_profile_image'] = Variable(hasProfileImage.value); + } + if (profileChangedAt.present) { + map['profile_changed_at'] = Variable(profileChangedAt.value); + } + if (avatarColor.present) { + map['avatar_color'] = Variable(avatarColor.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor') + ..write(')')) + .toString(); + } +} + +class RemoteAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn durationMs = GeneratedColumn( + 'duration_ms', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0 CHECK (is_favorite IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL REFERENCES user_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn localDateTime = GeneratedColumn( + 'local_date_time', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn thumbHash = GeneratedColumn( + 'thumb_hash', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn uploadedAt = GeneratedColumn( + 'uploaded_at', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn livePhotoVideoId = GeneratedColumn( + 'live_photo_video_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn visibility = GeneratedColumn( + 'visibility', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn stackId = GeneratedColumn( + 'stack_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn libraryId = GeneratedColumn( + 'library_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn isEdited = GeneratedColumn( + 'is_edited', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0 CHECK (is_edited IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationMs, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + uploadedAt, + livePhotoVideoId, + visibility, + stackId, + libraryId, + isEdited, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_asset_entity'; + @override + Set get $primaryKey => {id}; + @override + RemoteAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationMs: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_ms'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + )!, + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_favorite'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + localDateTime: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}local_date_time'], + ), + thumbHash: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumb_hash'], + ), + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}deleted_at'], + ), + uploadedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}uploaded_at'], + ), + livePhotoVideoId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}live_photo_video_id'], + ), + visibility: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}visibility'], + )!, + stackId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}stack_id'], + ), + libraryId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}library_id'], + ), + isEdited: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_edited'], + )!, + ); + } + + @override + RemoteAssetEntity createAlias(String alias) { + return RemoteAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class RemoteAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final String createdAt; + final String updatedAt; + final int? width; + final int? height; + final int? durationMs; + final String id; + final String checksum; + final int isFavorite; + final String ownerId; + final String? localDateTime; + final String? thumbHash; + final String? deletedAt; + final String? uploadedAt; + final String? livePhotoVideoId; + final int visibility; + final String? stackId; + final String? libraryId; + final int isEdited; + const RemoteAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationMs, + required this.id, + required this.checksum, + required this.isFavorite, + required this.ownerId, + this.localDateTime, + this.thumbHash, + this.deletedAt, + this.uploadedAt, + this.livePhotoVideoId, + required this.visibility, + this.stackId, + this.libraryId, + required this.isEdited, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationMs != null) { + map['duration_ms'] = Variable(durationMs); + } + map['id'] = Variable(id); + map['checksum'] = Variable(checksum); + map['is_favorite'] = Variable(isFavorite); + map['owner_id'] = Variable(ownerId); + if (!nullToAbsent || localDateTime != null) { + map['local_date_time'] = Variable(localDateTime); + } + if (!nullToAbsent || thumbHash != null) { + map['thumb_hash'] = Variable(thumbHash); + } + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + if (!nullToAbsent || uploadedAt != null) { + map['uploaded_at'] = Variable(uploadedAt); + } + if (!nullToAbsent || livePhotoVideoId != null) { + map['live_photo_video_id'] = Variable(livePhotoVideoId); + } + map['visibility'] = Variable(visibility); + if (!nullToAbsent || stackId != null) { + map['stack_id'] = Variable(stackId); + } + if (!nullToAbsent || libraryId != null) { + map['library_id'] = Variable(libraryId); + } + map['is_edited'] = Variable(isEdited); + return map; + } + + factory RemoteAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationMs: serializer.fromJson(json['durationMs']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + ownerId: serializer.fromJson(json['ownerId']), + localDateTime: serializer.fromJson(json['localDateTime']), + thumbHash: serializer.fromJson(json['thumbHash']), + deletedAt: serializer.fromJson(json['deletedAt']), + uploadedAt: serializer.fromJson(json['uploadedAt']), + livePhotoVideoId: serializer.fromJson(json['livePhotoVideoId']), + visibility: serializer.fromJson(json['visibility']), + stackId: serializer.fromJson(json['stackId']), + libraryId: serializer.fromJson(json['libraryId']), + isEdited: serializer.fromJson(json['isEdited']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationMs': serializer.toJson(durationMs), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'ownerId': serializer.toJson(ownerId), + 'localDateTime': serializer.toJson(localDateTime), + 'thumbHash': serializer.toJson(thumbHash), + 'deletedAt': serializer.toJson(deletedAt), + 'uploadedAt': serializer.toJson(uploadedAt), + 'livePhotoVideoId': serializer.toJson(livePhotoVideoId), + 'visibility': serializer.toJson(visibility), + 'stackId': serializer.toJson(stackId), + 'libraryId': serializer.toJson(libraryId), + 'isEdited': serializer.toJson(isEdited), + }; + } + + RemoteAssetEntityData copyWith({ + String? name, + int? type, + String? createdAt, + String? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationMs = const Value.absent(), + String? id, + String? checksum, + int? isFavorite, + String? ownerId, + Value localDateTime = const Value.absent(), + Value thumbHash = const Value.absent(), + Value deletedAt = const Value.absent(), + Value uploadedAt = const Value.absent(), + Value livePhotoVideoId = const Value.absent(), + int? visibility, + Value stackId = const Value.absent(), + Value libraryId = const Value.absent(), + int? isEdited, + }) => RemoteAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationMs: durationMs.present ? durationMs.value : this.durationMs, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime.present + ? localDateTime.value + : this.localDateTime, + thumbHash: thumbHash.present ? thumbHash.value : this.thumbHash, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + uploadedAt: uploadedAt.present ? uploadedAt.value : this.uploadedAt, + livePhotoVideoId: livePhotoVideoId.present + ? livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId.present ? stackId.value : this.stackId, + libraryId: libraryId.present ? libraryId.value : this.libraryId, + isEdited: isEdited ?? this.isEdited, + ); + RemoteAssetEntityData copyWithCompanion(RemoteAssetEntityCompanion data) { + return RemoteAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationMs: data.durationMs.present + ? data.durationMs.value + : this.durationMs, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + localDateTime: data.localDateTime.present + ? data.localDateTime.value + : this.localDateTime, + thumbHash: data.thumbHash.present ? data.thumbHash.value : this.thumbHash, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + uploadedAt: data.uploadedAt.present + ? data.uploadedAt.value + : this.uploadedAt, + livePhotoVideoId: data.livePhotoVideoId.present + ? data.livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: data.visibility.present + ? data.visibility.value + : this.visibility, + stackId: data.stackId.present ? data.stackId.value : this.stackId, + libraryId: data.libraryId.present ? data.libraryId.value : this.libraryId, + isEdited: data.isEdited.present ? data.isEdited.value : this.isEdited, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationMs: $durationMs, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('uploadedAt: $uploadedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId, ') + ..write('libraryId: $libraryId, ') + ..write('isEdited: $isEdited') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationMs, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + uploadedAt, + livePhotoVideoId, + visibility, + stackId, + libraryId, + isEdited, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationMs == this.durationMs && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.ownerId == this.ownerId && + other.localDateTime == this.localDateTime && + other.thumbHash == this.thumbHash && + other.deletedAt == this.deletedAt && + other.uploadedAt == this.uploadedAt && + other.livePhotoVideoId == this.livePhotoVideoId && + other.visibility == this.visibility && + other.stackId == this.stackId && + other.libraryId == this.libraryId && + other.isEdited == this.isEdited); +} + +class RemoteAssetEntityCompanion + extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationMs; + final Value id; + final Value checksum; + final Value isFavorite; + final Value ownerId; + final Value localDateTime; + final Value thumbHash; + final Value deletedAt; + final Value uploadedAt; + final Value livePhotoVideoId; + final Value visibility; + final Value stackId; + final Value libraryId; + final Value isEdited; + const RemoteAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationMs = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.ownerId = const Value.absent(), + this.localDateTime = const Value.absent(), + this.thumbHash = const Value.absent(), + this.deletedAt = const Value.absent(), + this.uploadedAt = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + this.visibility = const Value.absent(), + this.stackId = const Value.absent(), + this.libraryId = const Value.absent(), + this.isEdited = const Value.absent(), + }); + RemoteAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationMs = const Value.absent(), + required String id, + required String checksum, + this.isFavorite = const Value.absent(), + required String ownerId, + this.localDateTime = const Value.absent(), + this.thumbHash = const Value.absent(), + this.deletedAt = const Value.absent(), + this.uploadedAt = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + required int visibility, + this.stackId = const Value.absent(), + this.libraryId = const Value.absent(), + this.isEdited = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id), + checksum = Value(checksum), + ownerId = Value(ownerId), + visibility = Value(visibility); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationMs, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? ownerId, + Expression? localDateTime, + Expression? thumbHash, + Expression? deletedAt, + Expression? uploadedAt, + Expression? livePhotoVideoId, + Expression? visibility, + Expression? stackId, + Expression? libraryId, + Expression? isEdited, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationMs != null) 'duration_ms': durationMs, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (ownerId != null) 'owner_id': ownerId, + if (localDateTime != null) 'local_date_time': localDateTime, + if (thumbHash != null) 'thumb_hash': thumbHash, + if (deletedAt != null) 'deleted_at': deletedAt, + if (uploadedAt != null) 'uploaded_at': uploadedAt, + if (livePhotoVideoId != null) 'live_photo_video_id': livePhotoVideoId, + if (visibility != null) 'visibility': visibility, + if (stackId != null) 'stack_id': stackId, + if (libraryId != null) 'library_id': libraryId, + if (isEdited != null) 'is_edited': isEdited, + }); + } + + RemoteAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationMs, + Value? id, + Value? checksum, + Value? isFavorite, + Value? ownerId, + Value? localDateTime, + Value? thumbHash, + Value? deletedAt, + Value? uploadedAt, + Value? livePhotoVideoId, + Value? visibility, + Value? stackId, + Value? libraryId, + Value? isEdited, + }) { + return RemoteAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationMs: durationMs ?? this.durationMs, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime ?? this.localDateTime, + thumbHash: thumbHash ?? this.thumbHash, + deletedAt: deletedAt ?? this.deletedAt, + uploadedAt: uploadedAt ?? this.uploadedAt, + livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId ?? this.stackId, + libraryId: libraryId ?? this.libraryId, + isEdited: isEdited ?? this.isEdited, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationMs.present) { + map['duration_ms'] = Variable(durationMs.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (localDateTime.present) { + map['local_date_time'] = Variable(localDateTime.value); + } + if (thumbHash.present) { + map['thumb_hash'] = Variable(thumbHash.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + if (uploadedAt.present) { + map['uploaded_at'] = Variable(uploadedAt.value); + } + if (livePhotoVideoId.present) { + map['live_photo_video_id'] = Variable(livePhotoVideoId.value); + } + if (visibility.present) { + map['visibility'] = Variable(visibility.value); + } + if (stackId.present) { + map['stack_id'] = Variable(stackId.value); + } + if (libraryId.present) { + map['library_id'] = Variable(libraryId.value); + } + if (isEdited.present) { + map['is_edited'] = Variable(isEdited.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationMs: $durationMs, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('uploadedAt: $uploadedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId, ') + ..write('libraryId: $libraryId, ') + ..write('isEdited: $isEdited') + ..write(')')) + .toString(); + } +} + +class StackEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + StackEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL REFERENCES user_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn primaryAssetId = GeneratedColumn( + 'primary_asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + primaryAssetId, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'stack_entity'; + @override + Set get $primaryKey => {id}; + @override + StackEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return StackEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + primaryAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}primary_asset_id'], + )!, + ); + } + + @override + StackEntity createAlias(String alias) { + return StackEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class StackEntityData extends DataClass implements Insertable { + final String id; + final String createdAt; + final String updatedAt; + final String ownerId; + final String primaryAssetId; + const StackEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.primaryAssetId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + map['primary_asset_id'] = Variable(primaryAssetId); + return map; + } + + factory StackEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return StackEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + primaryAssetId: serializer.fromJson(json['primaryAssetId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'primaryAssetId': serializer.toJson(primaryAssetId), + }; + } + + StackEntityData copyWith({ + String? id, + String? createdAt, + String? updatedAt, + String? ownerId, + String? primaryAssetId, + }) => StackEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + StackEntityData copyWithCompanion(StackEntityCompanion data) { + return StackEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + primaryAssetId: data.primaryAssetId.present + ? data.primaryAssetId.value + : this.primaryAssetId, + ); + } + + @override + String toString() { + return (StringBuffer('StackEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(id, createdAt, updatedAt, ownerId, primaryAssetId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is StackEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.primaryAssetId == this.primaryAssetId); +} + +class StackEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value primaryAssetId; + const StackEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.primaryAssetId = const Value.absent(), + }); + StackEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + required String primaryAssetId, + }) : id = Value(id), + ownerId = Value(ownerId), + primaryAssetId = Value(primaryAssetId); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? primaryAssetId, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (primaryAssetId != null) 'primary_asset_id': primaryAssetId, + }); + } + + StackEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? primaryAssetId, + }) { + return StackEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (primaryAssetId.present) { + map['primary_asset_id'] = Variable(primaryAssetId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('StackEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } +} + +class LocalAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn durationMs = GeneratedColumn( + 'duration_ms', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0 CHECK (is_favorite IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn iCloudId = GeneratedColumn( + 'i_cloud_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn adjustmentTime = GeneratedColumn( + 'adjustment_time', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn playbackStyle = GeneratedColumn( + 'playback_style', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0', + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationMs, + id, + checksum, + isFavorite, + orientation, + iCloudId, + adjustmentTime, + latitude, + longitude, + playbackStyle, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_asset_entity'; + @override + Set get $primaryKey => {id}; + @override + LocalAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationMs: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_ms'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_favorite'], + )!, + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}orientation'], + )!, + iCloudId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}i_cloud_id'], + ), + adjustmentTime: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}adjustment_time'], + ), + latitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + playbackStyle: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}playback_style'], + )!, + ); + } + + @override + LocalAssetEntity createAlias(String alias) { + return LocalAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class LocalAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final String createdAt; + final String updatedAt; + final int? width; + final int? height; + final int? durationMs; + final String id; + final String? checksum; + final int isFavorite; + final int orientation; + final String? iCloudId; + final String? adjustmentTime; + final double? latitude; + final double? longitude; + final int playbackStyle; + const LocalAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationMs, + required this.id, + this.checksum, + required this.isFavorite, + required this.orientation, + this.iCloudId, + this.adjustmentTime, + this.latitude, + this.longitude, + required this.playbackStyle, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationMs != null) { + map['duration_ms'] = Variable(durationMs); + } + map['id'] = Variable(id); + if (!nullToAbsent || checksum != null) { + map['checksum'] = Variable(checksum); + } + map['is_favorite'] = Variable(isFavorite); + map['orientation'] = Variable(orientation); + if (!nullToAbsent || iCloudId != null) { + map['i_cloud_id'] = Variable(iCloudId); + } + if (!nullToAbsent || adjustmentTime != null) { + map['adjustment_time'] = Variable(adjustmentTime); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = Variable(longitude); + } + map['playback_style'] = Variable(playbackStyle); + return map; + } + + factory LocalAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationMs: serializer.fromJson(json['durationMs']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + orientation: serializer.fromJson(json['orientation']), + iCloudId: serializer.fromJson(json['iCloudId']), + adjustmentTime: serializer.fromJson(json['adjustmentTime']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + playbackStyle: serializer.fromJson(json['playbackStyle']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationMs': serializer.toJson(durationMs), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'orientation': serializer.toJson(orientation), + 'iCloudId': serializer.toJson(iCloudId), + 'adjustmentTime': serializer.toJson(adjustmentTime), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + 'playbackStyle': serializer.toJson(playbackStyle), + }; + } + + LocalAssetEntityData copyWith({ + String? name, + int? type, + String? createdAt, + String? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationMs = const Value.absent(), + String? id, + Value checksum = const Value.absent(), + int? isFavorite, + int? orientation, + Value iCloudId = const Value.absent(), + Value adjustmentTime = const Value.absent(), + Value latitude = const Value.absent(), + Value longitude = const Value.absent(), + int? playbackStyle, + }) => LocalAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationMs: durationMs.present ? durationMs.value : this.durationMs, + id: id ?? this.id, + checksum: checksum.present ? checksum.value : this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + iCloudId: iCloudId.present ? iCloudId.value : this.iCloudId, + adjustmentTime: adjustmentTime.present + ? adjustmentTime.value + : this.adjustmentTime, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + playbackStyle: playbackStyle ?? this.playbackStyle, + ); + LocalAssetEntityData copyWithCompanion(LocalAssetEntityCompanion data) { + return LocalAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationMs: data.durationMs.present + ? data.durationMs.value + : this.durationMs, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + iCloudId: data.iCloudId.present ? data.iCloudId.value : this.iCloudId, + adjustmentTime: data.adjustmentTime.present + ? data.adjustmentTime.value + : this.adjustmentTime, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + playbackStyle: data.playbackStyle.present + ? data.playbackStyle.value + : this.playbackStyle, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationMs: $durationMs, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation, ') + ..write('iCloudId: $iCloudId, ') + ..write('adjustmentTime: $adjustmentTime, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('playbackStyle: $playbackStyle') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationMs, + id, + checksum, + isFavorite, + orientation, + iCloudId, + adjustmentTime, + latitude, + longitude, + playbackStyle, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationMs == this.durationMs && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.orientation == this.orientation && + other.iCloudId == this.iCloudId && + other.adjustmentTime == this.adjustmentTime && + other.latitude == this.latitude && + other.longitude == this.longitude && + other.playbackStyle == this.playbackStyle); +} + +class LocalAssetEntityCompanion extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationMs; + final Value id; + final Value checksum; + final Value isFavorite; + final Value orientation; + final Value iCloudId; + final Value adjustmentTime; + final Value latitude; + final Value longitude; + final Value playbackStyle; + const LocalAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationMs = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + this.iCloudId = const Value.absent(), + this.adjustmentTime = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.playbackStyle = const Value.absent(), + }); + LocalAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationMs = const Value.absent(), + required String id, + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + this.iCloudId = const Value.absent(), + this.adjustmentTime = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.playbackStyle = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationMs, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? orientation, + Expression? iCloudId, + Expression? adjustmentTime, + Expression? latitude, + Expression? longitude, + Expression? playbackStyle, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationMs != null) 'duration_ms': durationMs, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (orientation != null) 'orientation': orientation, + if (iCloudId != null) 'i_cloud_id': iCloudId, + if (adjustmentTime != null) 'adjustment_time': adjustmentTime, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + if (playbackStyle != null) 'playback_style': playbackStyle, + }); + } + + LocalAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationMs, + Value? id, + Value? checksum, + Value? isFavorite, + Value? orientation, + Value? iCloudId, + Value? adjustmentTime, + Value? latitude, + Value? longitude, + Value? playbackStyle, + }) { + return LocalAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationMs: durationMs ?? this.durationMs, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + iCloudId: iCloudId ?? this.iCloudId, + adjustmentTime: adjustmentTime ?? this.adjustmentTime, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + playbackStyle: playbackStyle ?? this.playbackStyle, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationMs.present) { + map['duration_ms'] = Variable(durationMs.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + if (iCloudId.present) { + map['i_cloud_id'] = Variable(iCloudId.value); + } + if (adjustmentTime.present) { + map['adjustment_time'] = Variable(adjustmentTime.value); + } + if (latitude.present) { + map['latitude'] = Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = Variable(longitude.value); + } + if (playbackStyle.present) { + map['playback_style'] = Variable(playbackStyle.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationMs: $durationMs, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation, ') + ..write('iCloudId: $iCloudId, ') + ..write('adjustmentTime: $adjustmentTime, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('playbackStyle: $playbackStyle') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT \'\'', + defaultValue: const CustomExpression('\'\''), + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn thumbnailAssetId = GeneratedColumn( + 'thumbnail_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: + 'NULL REFERENCES remote_asset_entity(id)ON DELETE SET NULL', + ); + late final GeneratedColumn isActivityEnabled = GeneratedColumn( + 'is_activity_enabled', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: + 'NOT NULL DEFAULT 1 CHECK (is_activity_enabled IN (0, 1))', + defaultValue: const CustomExpression('1'), + ); + late final GeneratedColumn order = GeneratedColumn( + 'order', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + @override + List get $columns => [ + id, + name, + description, + createdAt, + updatedAt, + thumbnailAssetId, + isActivityEnabled, + order, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_entity'; + @override + Set get $primaryKey => {id}; + @override + RemoteAlbumEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + thumbnailAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumbnail_asset_id'], + ), + isActivityEnabled: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_activity_enabled'], + )!, + order: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}order'], + )!, + ); + } + + @override + RemoteAlbumEntity createAlias(String alias) { + return RemoteAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class RemoteAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String description; + final String createdAt; + final String updatedAt; + final String? thumbnailAssetId; + final int isActivityEnabled; + final int order; + const RemoteAlbumEntityData({ + required this.id, + required this.name, + required this.description, + required this.createdAt, + required this.updatedAt, + this.thumbnailAssetId, + required this.isActivityEnabled, + required this.order, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['description'] = Variable(description); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || thumbnailAssetId != null) { + map['thumbnail_asset_id'] = Variable(thumbnailAssetId); + } + map['is_activity_enabled'] = Variable(isActivityEnabled); + map['order'] = Variable(order); + return map; + } + + factory RemoteAlbumEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + description: serializer.fromJson(json['description']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + thumbnailAssetId: serializer.fromJson(json['thumbnailAssetId']), + isActivityEnabled: serializer.fromJson(json['isActivityEnabled']), + order: serializer.fromJson(json['order']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'description': serializer.toJson(description), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'thumbnailAssetId': serializer.toJson(thumbnailAssetId), + 'isActivityEnabled': serializer.toJson(isActivityEnabled), + 'order': serializer.toJson(order), + }; + } + + RemoteAlbumEntityData copyWith({ + String? id, + String? name, + String? description, + String? createdAt, + String? updatedAt, + Value thumbnailAssetId = const Value.absent(), + int? isActivityEnabled, + int? order, + }) => RemoteAlbumEntityData( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + thumbnailAssetId: thumbnailAssetId.present + ? thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + RemoteAlbumEntityData copyWithCompanion(RemoteAlbumEntityCompanion data) { + return RemoteAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + description: data.description.present + ? data.description.value + : this.description, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + thumbnailAssetId: data.thumbnailAssetId.present + ? data.thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: data.isActivityEnabled.present + ? data.isActivityEnabled.value + : this.isActivityEnabled, + order: data.order.present ? data.order.value : this.order, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + description, + createdAt, + updatedAt, + thumbnailAssetId, + isActivityEnabled, + order, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.description == this.description && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.thumbnailAssetId == this.thumbnailAssetId && + other.isActivityEnabled == this.isActivityEnabled && + other.order == this.order); +} + +class RemoteAlbumEntityCompanion + extends UpdateCompanion { + final Value id; + final Value name; + final Value description; + final Value createdAt; + final Value updatedAt; + final Value thumbnailAssetId; + final Value isActivityEnabled; + final Value order; + const RemoteAlbumEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.description = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + this.order = const Value.absent(), + }); + RemoteAlbumEntityCompanion.insert({ + required String id, + required String name, + this.description = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + required int order, + }) : id = Value(id), + name = Value(name), + order = Value(order); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? description, + Expression? createdAt, + Expression? updatedAt, + Expression? thumbnailAssetId, + Expression? isActivityEnabled, + Expression? order, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (description != null) 'description': description, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (thumbnailAssetId != null) 'thumbnail_asset_id': thumbnailAssetId, + if (isActivityEnabled != null) 'is_activity_enabled': isActivityEnabled, + if (order != null) 'order': order, + }); + } + + RemoteAlbumEntityCompanion copyWith({ + Value? id, + Value? name, + Value? description, + Value? createdAt, + Value? updatedAt, + Value? thumbnailAssetId, + Value? isActivityEnabled, + Value? order, + }) { + return RemoteAlbumEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + thumbnailAssetId: thumbnailAssetId ?? this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (thumbnailAssetId.present) { + map['thumbnail_asset_id'] = Variable(thumbnailAssetId.value); + } + if (isActivityEnabled.present) { + map['is_activity_enabled'] = Variable(isActivityEnabled.value); + } + if (order.present) { + map['order'] = Variable(order.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } +} + +class LocalAlbumEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAlbumEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn backupSelection = GeneratedColumn( + 'backup_selection', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn isIosSharedAlbum = GeneratedColumn( + 'is_ios_shared_album', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: + 'NOT NULL DEFAULT 0 CHECK (is_ios_shared_album IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn linkedRemoteAlbumId = + GeneratedColumn( + 'linked_remote_album_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: + 'NULL REFERENCES remote_album_entity(id)ON DELETE SET NULL', + ); + late final GeneratedColumn marker = GeneratedColumn( + 'marker', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL CHECK (marker IN (0, 1))', + ); + @override + List get $columns => [ + id, + name, + updatedAt, + backupSelection, + isIosSharedAlbum, + linkedRemoteAlbumId, + marker, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_entity'; + @override + Set get $primaryKey => {id}; + @override + LocalAlbumEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAlbumEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + backupSelection: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}backup_selection'], + )!, + isIosSharedAlbum: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_ios_shared_album'], + )!, + linkedRemoteAlbumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}linked_remote_album_id'], + ), + marker: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}marker'], + ), + ); + } + + @override + LocalAlbumEntity createAlias(String alias) { + return LocalAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class LocalAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String updatedAt; + final int backupSelection; + final int isIosSharedAlbum; + final String? linkedRemoteAlbumId; + final int? marker; + const LocalAlbumEntityData({ + required this.id, + required this.name, + required this.updatedAt, + required this.backupSelection, + required this.isIosSharedAlbum, + this.linkedRemoteAlbumId, + this.marker, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['updated_at'] = Variable(updatedAt); + map['backup_selection'] = Variable(backupSelection); + map['is_ios_shared_album'] = Variable(isIosSharedAlbum); + if (!nullToAbsent || linkedRemoteAlbumId != null) { + map['linked_remote_album_id'] = Variable(linkedRemoteAlbumId); + } + if (!nullToAbsent || marker != null) { + map['marker'] = Variable(marker); + } + return map; + } + + factory LocalAlbumEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + updatedAt: serializer.fromJson(json['updatedAt']), + backupSelection: serializer.fromJson(json['backupSelection']), + isIosSharedAlbum: serializer.fromJson(json['isIosSharedAlbum']), + linkedRemoteAlbumId: serializer.fromJson( + json['linkedRemoteAlbumId'], + ), + marker: serializer.fromJson(json['marker']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'updatedAt': serializer.toJson(updatedAt), + 'backupSelection': serializer.toJson(backupSelection), + 'isIosSharedAlbum': serializer.toJson(isIosSharedAlbum), + 'linkedRemoteAlbumId': serializer.toJson(linkedRemoteAlbumId), + 'marker': serializer.toJson(marker), + }; + } + + LocalAlbumEntityData copyWith({ + String? id, + String? name, + String? updatedAt, + int? backupSelection, + int? isIosSharedAlbum, + Value linkedRemoteAlbumId = const Value.absent(), + Value marker = const Value.absent(), + }) => LocalAlbumEntityData( + id: id ?? this.id, + name: name ?? this.name, + updatedAt: updatedAt ?? this.updatedAt, + backupSelection: backupSelection ?? this.backupSelection, + isIosSharedAlbum: isIosSharedAlbum ?? this.isIosSharedAlbum, + linkedRemoteAlbumId: linkedRemoteAlbumId.present + ? linkedRemoteAlbumId.value + : this.linkedRemoteAlbumId, + marker: marker.present ? marker.value : this.marker, + ); + LocalAlbumEntityData copyWithCompanion(LocalAlbumEntityCompanion data) { + return LocalAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + backupSelection: data.backupSelection.present + ? data.backupSelection.value + : this.backupSelection, + isIosSharedAlbum: data.isIosSharedAlbum.present + ? data.isIosSharedAlbum.value + : this.isIosSharedAlbum, + linkedRemoteAlbumId: data.linkedRemoteAlbumId.present + ? data.linkedRemoteAlbumId.value + : this.linkedRemoteAlbumId, + marker: data.marker.present ? data.marker.value : this.marker, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('updatedAt: $updatedAt, ') + ..write('backupSelection: $backupSelection, ') + ..write('isIosSharedAlbum: $isIosSharedAlbum, ') + ..write('linkedRemoteAlbumId: $linkedRemoteAlbumId, ') + ..write('marker: $marker') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + updatedAt, + backupSelection, + isIosSharedAlbum, + linkedRemoteAlbumId, + marker, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.updatedAt == this.updatedAt && + other.backupSelection == this.backupSelection && + other.isIosSharedAlbum == this.isIosSharedAlbum && + other.linkedRemoteAlbumId == this.linkedRemoteAlbumId && + other.marker == this.marker); +} + +class LocalAlbumEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value updatedAt; + final Value backupSelection; + final Value isIosSharedAlbum; + final Value linkedRemoteAlbumId; + final Value marker; + const LocalAlbumEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.updatedAt = const Value.absent(), + this.backupSelection = const Value.absent(), + this.isIosSharedAlbum = const Value.absent(), + this.linkedRemoteAlbumId = const Value.absent(), + this.marker = const Value.absent(), + }); + LocalAlbumEntityCompanion.insert({ + required String id, + required String name, + this.updatedAt = const Value.absent(), + required int backupSelection, + this.isIosSharedAlbum = const Value.absent(), + this.linkedRemoteAlbumId = const Value.absent(), + this.marker = const Value.absent(), + }) : id = Value(id), + name = Value(name), + backupSelection = Value(backupSelection); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? updatedAt, + Expression? backupSelection, + Expression? isIosSharedAlbum, + Expression? linkedRemoteAlbumId, + Expression? marker, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (updatedAt != null) 'updated_at': updatedAt, + if (backupSelection != null) 'backup_selection': backupSelection, + if (isIosSharedAlbum != null) 'is_ios_shared_album': isIosSharedAlbum, + if (linkedRemoteAlbumId != null) + 'linked_remote_album_id': linkedRemoteAlbumId, + if (marker != null) 'marker': marker, + }); + } + + LocalAlbumEntityCompanion copyWith({ + Value? id, + Value? name, + Value? updatedAt, + Value? backupSelection, + Value? isIosSharedAlbum, + Value? linkedRemoteAlbumId, + Value? marker, + }) { + return LocalAlbumEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + updatedAt: updatedAt ?? this.updatedAt, + backupSelection: backupSelection ?? this.backupSelection, + isIosSharedAlbum: isIosSharedAlbum ?? this.isIosSharedAlbum, + linkedRemoteAlbumId: linkedRemoteAlbumId ?? this.linkedRemoteAlbumId, + marker: marker ?? this.marker, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (backupSelection.present) { + map['backup_selection'] = Variable(backupSelection.value); + } + if (isIosSharedAlbum.present) { + map['is_ios_shared_album'] = Variable(isIosSharedAlbum.value); + } + if (linkedRemoteAlbumId.present) { + map['linked_remote_album_id'] = Variable( + linkedRemoteAlbumId.value, + ); + } + if (marker.present) { + map['marker'] = Variable(marker.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('updatedAt: $updatedAt, ') + ..write('backupSelection: $backupSelection, ') + ..write('isIosSharedAlbum: $isIosSharedAlbum, ') + ..write('linkedRemoteAlbumId: $linkedRemoteAlbumId, ') + ..write('marker: $marker') + ..write(')')) + .toString(); + } +} + +class LocalAlbumAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAlbumAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES local_asset_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES local_album_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn marker = GeneratedColumn( + 'marker', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL CHECK (marker IN (0, 1))', + ); + @override + List get $columns => [assetId, albumId, marker]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_asset_entity'; + @override + Set get $primaryKey => {assetId, albumId}; + @override + LocalAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + marker: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}marker'], + ), + ); + } + + @override + LocalAlbumAssetEntity createAlias(String alias) { + return LocalAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const [ + 'PRIMARY KEY(asset_id, album_id)', + ]; + @override + bool get dontWriteConstraints => true; +} + +class LocalAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + final int? marker; + const LocalAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + this.marker, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['album_id'] = Variable(albumId); + if (!nullToAbsent || marker != null) { + map['marker'] = Variable(marker); + } + return map; + } + + factory LocalAlbumAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + marker: serializer.fromJson(json['marker']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + 'marker': serializer.toJson(marker), + }; + } + + LocalAlbumAssetEntityData copyWith({ + String? assetId, + String? albumId, + Value marker = const Value.absent(), + }) => LocalAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + marker: marker.present ? marker.value : this.marker, + ); + LocalAlbumAssetEntityData copyWithCompanion( + LocalAlbumAssetEntityCompanion data, + ) { + return LocalAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + marker: data.marker.present ? data.marker.value : this.marker, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId, ') + ..write('marker: $marker') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId, marker); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId && + other.marker == this.marker); +} + +class LocalAlbumAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value albumId; + final Value marker; + const LocalAlbumAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.albumId = const Value.absent(), + this.marker = const Value.absent(), + }); + LocalAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + this.marker = const Value.absent(), + }) : assetId = Value(assetId), + albumId = Value(albumId); + static Insertable custom({ + Expression? assetId, + Expression? albumId, + Expression? marker, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + if (marker != null) 'marker': marker, + }); + } + + LocalAlbumAssetEntityCompanion copyWith({ + Value? assetId, + Value? albumId, + Value? marker, + }) { + return LocalAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + marker: marker ?? this.marker, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (marker.present) { + map['marker'] = Variable(marker.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId, ') + ..write('marker: $marker') + ..write(')')) + .toString(); + } +} + +class AuthUserEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AuthUserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn email = GeneratedColumn( + 'email', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn isAdmin = GeneratedColumn( + 'is_admin', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0 CHECK (is_admin IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn hasProfileImage = GeneratedColumn( + 'has_profile_image', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: + 'NOT NULL DEFAULT 0 CHECK (has_profile_image IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn profileChangedAt = GeneratedColumn( + 'profile_changed_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn avatarColor = GeneratedColumn( + 'avatar_color', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn quotaSizeInBytes = GeneratedColumn( + 'quota_size_in_bytes', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn quotaUsageInBytes = GeneratedColumn( + 'quota_usage_in_bytes', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn pinCode = GeneratedColumn( + 'pin_code', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + @override + List get $columns => [ + id, + name, + email, + isAdmin, + hasProfileImage, + profileChangedAt, + avatarColor, + quotaSizeInBytes, + quotaUsageInBytes, + pinCode, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'auth_user_entity'; + @override + Set get $primaryKey => {id}; + @override + AuthUserEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AuthUserEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + email: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}email'], + )!, + isAdmin: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_admin'], + )!, + hasProfileImage: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}has_profile_image'], + )!, + profileChangedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}profile_changed_at'], + )!, + avatarColor: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}avatar_color'], + )!, + quotaSizeInBytes: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}quota_size_in_bytes'], + )!, + quotaUsageInBytes: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}quota_usage_in_bytes'], + )!, + pinCode: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}pin_code'], + ), + ); + } + + @override + AuthUserEntity createAlias(String alias) { + return AuthUserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class AuthUserEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String email; + final int isAdmin; + final int hasProfileImage; + final String profileChangedAt; + final int avatarColor; + final int quotaSizeInBytes; + final int quotaUsageInBytes; + final String? pinCode; + const AuthUserEntityData({ + required this.id, + required this.name, + required this.email, + required this.isAdmin, + required this.hasProfileImage, + required this.profileChangedAt, + required this.avatarColor, + required this.quotaSizeInBytes, + required this.quotaUsageInBytes, + this.pinCode, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['email'] = Variable(email); + map['is_admin'] = Variable(isAdmin); + map['has_profile_image'] = Variable(hasProfileImage); + map['profile_changed_at'] = Variable(profileChangedAt); + map['avatar_color'] = Variable(avatarColor); + map['quota_size_in_bytes'] = Variable(quotaSizeInBytes); + map['quota_usage_in_bytes'] = Variable(quotaUsageInBytes); + if (!nullToAbsent || pinCode != null) { + map['pin_code'] = Variable(pinCode); + } + return map; + } + + factory AuthUserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AuthUserEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + email: serializer.fromJson(json['email']), + isAdmin: serializer.fromJson(json['isAdmin']), + hasProfileImage: serializer.fromJson(json['hasProfileImage']), + profileChangedAt: serializer.fromJson(json['profileChangedAt']), + avatarColor: serializer.fromJson(json['avatarColor']), + quotaSizeInBytes: serializer.fromJson(json['quotaSizeInBytes']), + quotaUsageInBytes: serializer.fromJson(json['quotaUsageInBytes']), + pinCode: serializer.fromJson(json['pinCode']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'email': serializer.toJson(email), + 'isAdmin': serializer.toJson(isAdmin), + 'hasProfileImage': serializer.toJson(hasProfileImage), + 'profileChangedAt': serializer.toJson(profileChangedAt), + 'avatarColor': serializer.toJson(avatarColor), + 'quotaSizeInBytes': serializer.toJson(quotaSizeInBytes), + 'quotaUsageInBytes': serializer.toJson(quotaUsageInBytes), + 'pinCode': serializer.toJson(pinCode), + }; + } + + AuthUserEntityData copyWith({ + String? id, + String? name, + String? email, + int? isAdmin, + int? hasProfileImage, + String? profileChangedAt, + int? avatarColor, + int? quotaSizeInBytes, + int? quotaUsageInBytes, + Value pinCode = const Value.absent(), + }) => AuthUserEntityData( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + isAdmin: isAdmin ?? this.isAdmin, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + quotaSizeInBytes: quotaSizeInBytes ?? this.quotaSizeInBytes, + quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes, + pinCode: pinCode.present ? pinCode.value : this.pinCode, + ); + AuthUserEntityData copyWithCompanion(AuthUserEntityCompanion data) { + return AuthUserEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + email: data.email.present ? data.email.value : this.email, + isAdmin: data.isAdmin.present ? data.isAdmin.value : this.isAdmin, + hasProfileImage: data.hasProfileImage.present + ? data.hasProfileImage.value + : this.hasProfileImage, + profileChangedAt: data.profileChangedAt.present + ? data.profileChangedAt.value + : this.profileChangedAt, + avatarColor: data.avatarColor.present + ? data.avatarColor.value + : this.avatarColor, + quotaSizeInBytes: data.quotaSizeInBytes.present + ? data.quotaSizeInBytes.value + : this.quotaSizeInBytes, + quotaUsageInBytes: data.quotaUsageInBytes.present + ? data.quotaUsageInBytes.value + : this.quotaUsageInBytes, + pinCode: data.pinCode.present ? data.pinCode.value : this.pinCode, + ); + } + + @override + String toString() { + return (StringBuffer('AuthUserEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('isAdmin: $isAdmin, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor, ') + ..write('quotaSizeInBytes: $quotaSizeInBytes, ') + ..write('quotaUsageInBytes: $quotaUsageInBytes, ') + ..write('pinCode: $pinCode') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + email, + isAdmin, + hasProfileImage, + profileChangedAt, + avatarColor, + quotaSizeInBytes, + quotaUsageInBytes, + pinCode, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AuthUserEntityData && + other.id == this.id && + other.name == this.name && + other.email == this.email && + other.isAdmin == this.isAdmin && + other.hasProfileImage == this.hasProfileImage && + other.profileChangedAt == this.profileChangedAt && + other.avatarColor == this.avatarColor && + other.quotaSizeInBytes == this.quotaSizeInBytes && + other.quotaUsageInBytes == this.quotaUsageInBytes && + other.pinCode == this.pinCode); +} + +class AuthUserEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value email; + final Value isAdmin; + final Value hasProfileImage; + final Value profileChangedAt; + final Value avatarColor; + final Value quotaSizeInBytes; + final Value quotaUsageInBytes; + final Value pinCode; + const AuthUserEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.email = const Value.absent(), + this.isAdmin = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + this.quotaSizeInBytes = const Value.absent(), + this.quotaUsageInBytes = const Value.absent(), + this.pinCode = const Value.absent(), + }); + AuthUserEntityCompanion.insert({ + required String id, + required String name, + required String email, + this.isAdmin = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + required int avatarColor, + this.quotaSizeInBytes = const Value.absent(), + this.quotaUsageInBytes = const Value.absent(), + this.pinCode = const Value.absent(), + }) : id = Value(id), + name = Value(name), + email = Value(email), + avatarColor = Value(avatarColor); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? email, + Expression? isAdmin, + Expression? hasProfileImage, + Expression? profileChangedAt, + Expression? avatarColor, + Expression? quotaSizeInBytes, + Expression? quotaUsageInBytes, + Expression? pinCode, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (email != null) 'email': email, + if (isAdmin != null) 'is_admin': isAdmin, + if (hasProfileImage != null) 'has_profile_image': hasProfileImage, + if (profileChangedAt != null) 'profile_changed_at': profileChangedAt, + if (avatarColor != null) 'avatar_color': avatarColor, + if (quotaSizeInBytes != null) 'quota_size_in_bytes': quotaSizeInBytes, + if (quotaUsageInBytes != null) 'quota_usage_in_bytes': quotaUsageInBytes, + if (pinCode != null) 'pin_code': pinCode, + }); + } + + AuthUserEntityCompanion copyWith({ + Value? id, + Value? name, + Value? email, + Value? isAdmin, + Value? hasProfileImage, + Value? profileChangedAt, + Value? avatarColor, + Value? quotaSizeInBytes, + Value? quotaUsageInBytes, + Value? pinCode, + }) { + return AuthUserEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + isAdmin: isAdmin ?? this.isAdmin, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + quotaSizeInBytes: quotaSizeInBytes ?? this.quotaSizeInBytes, + quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes, + pinCode: pinCode ?? this.pinCode, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (email.present) { + map['email'] = Variable(email.value); + } + if (isAdmin.present) { + map['is_admin'] = Variable(isAdmin.value); + } + if (hasProfileImage.present) { + map['has_profile_image'] = Variable(hasProfileImage.value); + } + if (profileChangedAt.present) { + map['profile_changed_at'] = Variable(profileChangedAt.value); + } + if (avatarColor.present) { + map['avatar_color'] = Variable(avatarColor.value); + } + if (quotaSizeInBytes.present) { + map['quota_size_in_bytes'] = Variable(quotaSizeInBytes.value); + } + if (quotaUsageInBytes.present) { + map['quota_usage_in_bytes'] = Variable(quotaUsageInBytes.value); + } + if (pinCode.present) { + map['pin_code'] = Variable(pinCode.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AuthUserEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('isAdmin: $isAdmin, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor, ') + ..write('quotaSizeInBytes: $quotaSizeInBytes, ') + ..write('quotaUsageInBytes: $quotaUsageInBytes, ') + ..write('pinCode: $pinCode') + ..write(')')) + .toString(); + } +} + +class UserMetadataEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UserMetadataEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL REFERENCES user_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn key = GeneratedColumn( + 'key', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn value = + GeneratedColumn( + 'value', + aliasedName, + false, + type: DriftSqlType.blob, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + @override + List get $columns => [userId, key, value]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_metadata_entity'; + @override + Set get $primaryKey => {userId, key}; + @override + UserMetadataEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserMetadataEntityData( + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + key: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}key'], + )!, + value: attachedDatabase.typeMapping.read( + DriftSqlType.blob, + data['${effectivePrefix}value'], + )!, + ); + } + + @override + UserMetadataEntity createAlias(String alias) { + return UserMetadataEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(user_id, "key")']; + @override + bool get dontWriteConstraints => true; +} + +class UserMetadataEntityData extends DataClass + implements Insertable { + final String userId; + final int key; + final i2.Uint8List value; + const UserMetadataEntityData({ + required this.userId, + required this.key, + required this.value, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['user_id'] = Variable(userId); + map['key'] = Variable(key); + map['value'] = Variable(value); + return map; + } + + factory UserMetadataEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserMetadataEntityData( + userId: serializer.fromJson(json['userId']), + key: serializer.fromJson(json['key']), + value: serializer.fromJson(json['value']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'userId': serializer.toJson(userId), + 'key': serializer.toJson(key), + 'value': serializer.toJson(value), + }; + } + + UserMetadataEntityData copyWith({ + String? userId, + int? key, + i2.Uint8List? value, + }) => UserMetadataEntityData( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + UserMetadataEntityData copyWithCompanion(UserMetadataEntityCompanion data) { + return UserMetadataEntityData( + userId: data.userId.present ? data.userId.value : this.userId, + key: data.key.present ? data.key.value : this.key, + value: data.value.present ? data.value.value : this.value, + ); + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityData(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(userId, key, $driftBlobEquality.hash(value)); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserMetadataEntityData && + other.userId == this.userId && + other.key == this.key && + $driftBlobEquality.equals(other.value, this.value)); +} + +class UserMetadataEntityCompanion + extends UpdateCompanion { + final Value userId; + final Value key; + final Value value; + const UserMetadataEntityCompanion({ + this.userId = const Value.absent(), + this.key = const Value.absent(), + this.value = const Value.absent(), + }); + UserMetadataEntityCompanion.insert({ + required String userId, + required int key, + required i2.Uint8List value, + }) : userId = Value(userId), + key = Value(key), + value = Value(value); + static Insertable custom({ + Expression? userId, + Expression? key, + Expression? value, + }) { + return RawValuesInsertable({ + if (userId != null) 'user_id': userId, + if (key != null) 'key': key, + if (value != null) 'value': value, + }); + } + + UserMetadataEntityCompanion copyWith({ + Value? userId, + Value? key, + Value? value, + }) { + return UserMetadataEntityCompanion( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (key.present) { + map['key'] = Variable(key.value); + } + if (value.present) { + map['value'] = Variable(value.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityCompanion(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } +} + +class PartnerEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PartnerEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn sharedById = GeneratedColumn( + 'shared_by_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL REFERENCES user_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn sharedWithId = GeneratedColumn( + 'shared_with_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL REFERENCES user_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn inTimeline = GeneratedColumn( + 'in_timeline', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0 CHECK (in_timeline IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [sharedById, sharedWithId, inTimeline]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'partner_entity'; + @override + Set get $primaryKey => {sharedById, sharedWithId}; + @override + PartnerEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PartnerEntityData( + sharedById: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}shared_by_id'], + )!, + sharedWithId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}shared_with_id'], + )!, + inTimeline: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}in_timeline'], + )!, + ); + } + + @override + PartnerEntity createAlias(String alias) { + return PartnerEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const [ + 'PRIMARY KEY(shared_by_id, shared_with_id)', + ]; + @override + bool get dontWriteConstraints => true; +} + +class PartnerEntityData extends DataClass + implements Insertable { + final String sharedById; + final String sharedWithId; + final int inTimeline; + const PartnerEntityData({ + required this.sharedById, + required this.sharedWithId, + required this.inTimeline, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['shared_by_id'] = Variable(sharedById); + map['shared_with_id'] = Variable(sharedWithId); + map['in_timeline'] = Variable(inTimeline); + return map; + } + + factory PartnerEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PartnerEntityData( + sharedById: serializer.fromJson(json['sharedById']), + sharedWithId: serializer.fromJson(json['sharedWithId']), + inTimeline: serializer.fromJson(json['inTimeline']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'sharedById': serializer.toJson(sharedById), + 'sharedWithId': serializer.toJson(sharedWithId), + 'inTimeline': serializer.toJson(inTimeline), + }; + } + + PartnerEntityData copyWith({ + String? sharedById, + String? sharedWithId, + int? inTimeline, + }) => PartnerEntityData( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + PartnerEntityData copyWithCompanion(PartnerEntityCompanion data) { + return PartnerEntityData( + sharedById: data.sharedById.present + ? data.sharedById.value + : this.sharedById, + sharedWithId: data.sharedWithId.present + ? data.sharedWithId.value + : this.sharedWithId, + inTimeline: data.inTimeline.present + ? data.inTimeline.value + : this.inTimeline, + ); + } + + @override + String toString() { + return (StringBuffer('PartnerEntityData(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(sharedById, sharedWithId, inTimeline); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PartnerEntityData && + other.sharedById == this.sharedById && + other.sharedWithId == this.sharedWithId && + other.inTimeline == this.inTimeline); +} + +class PartnerEntityCompanion extends UpdateCompanion { + final Value sharedById; + final Value sharedWithId; + final Value inTimeline; + const PartnerEntityCompanion({ + this.sharedById = const Value.absent(), + this.sharedWithId = const Value.absent(), + this.inTimeline = const Value.absent(), + }); + PartnerEntityCompanion.insert({ + required String sharedById, + required String sharedWithId, + this.inTimeline = const Value.absent(), + }) : sharedById = Value(sharedById), + sharedWithId = Value(sharedWithId); + static Insertable custom({ + Expression? sharedById, + Expression? sharedWithId, + Expression? inTimeline, + }) { + return RawValuesInsertable({ + if (sharedById != null) 'shared_by_id': sharedById, + if (sharedWithId != null) 'shared_with_id': sharedWithId, + if (inTimeline != null) 'in_timeline': inTimeline, + }); + } + + PartnerEntityCompanion copyWith({ + Value? sharedById, + Value? sharedWithId, + Value? inTimeline, + }) { + return PartnerEntityCompanion( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (sharedById.present) { + map['shared_by_id'] = Variable(sharedById.value); + } + if (sharedWithId.present) { + map['shared_with_id'] = Variable(sharedWithId.value); + } + if (inTimeline.present) { + map['in_timeline'] = Variable(inTimeline.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PartnerEntityCompanion(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } +} + +class RemoteExifEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteExifEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES remote_asset_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn city = GeneratedColumn( + 'city', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn state = GeneratedColumn( + 'state', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn country = GeneratedColumn( + 'country', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn dateTimeOriginal = GeneratedColumn( + 'date_time_original', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn exposureTime = GeneratedColumn( + 'exposure_time', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn fNumber = GeneratedColumn( + 'f_number', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn fileSize = GeneratedColumn( + 'file_size', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn focalLength = GeneratedColumn( + 'focal_length', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn iso = GeneratedColumn( + 'iso', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn make = GeneratedColumn( + 'make', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn model = GeneratedColumn( + 'model', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn lens = GeneratedColumn( + 'lens', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn timeZone = GeneratedColumn( + 'time_zone', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn rating = GeneratedColumn( + 'rating', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn projectionType = GeneratedColumn( + 'projection_type', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + @override + List get $columns => [ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_exif_entity'; + @override + Set get $primaryKey => {assetId}; + @override + RemoteExifEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteExifEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + city: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}city'], + ), + state: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}state'], + ), + country: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}country'], + ), + dateTimeOriginal: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}date_time_original'], + ), + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + exposureTime: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}exposure_time'], + ), + fNumber: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}f_number'], + ), + fileSize: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}file_size'], + ), + focalLength: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}focal_length'], + ), + latitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + iso: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}iso'], + ), + make: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}make'], + ), + model: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}model'], + ), + lens: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}lens'], + ), + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}orientation'], + ), + timeZone: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}time_zone'], + ), + rating: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}rating'], + ), + projectionType: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}projection_type'], + ), + ); + } + + @override + RemoteExifEntity createAlias(String alias) { + return RemoteExifEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(asset_id)']; + @override + bool get dontWriteConstraints => true; +} + +class RemoteExifEntityData extends DataClass + implements Insertable { + final String assetId; + final String? city; + final String? state; + final String? country; + final String? dateTimeOriginal; + final String? description; + final int? height; + final int? width; + final String? exposureTime; + final double? fNumber; + final int? fileSize; + final double? focalLength; + final double? latitude; + final double? longitude; + final int? iso; + final String? make; + final String? model; + final String? lens; + final String? orientation; + final String? timeZone; + final int? rating; + final String? projectionType; + const RemoteExifEntityData({ + required this.assetId, + this.city, + this.state, + this.country, + this.dateTimeOriginal, + this.description, + this.height, + this.width, + this.exposureTime, + this.fNumber, + this.fileSize, + this.focalLength, + this.latitude, + this.longitude, + this.iso, + this.make, + this.model, + this.lens, + this.orientation, + this.timeZone, + this.rating, + this.projectionType, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || city != null) { + map['city'] = Variable(city); + } + if (!nullToAbsent || state != null) { + map['state'] = Variable(state); + } + if (!nullToAbsent || country != null) { + map['country'] = Variable(country); + } + if (!nullToAbsent || dateTimeOriginal != null) { + map['date_time_original'] = Variable(dateTimeOriginal); + } + if (!nullToAbsent || description != null) { + map['description'] = Variable(description); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || exposureTime != null) { + map['exposure_time'] = Variable(exposureTime); + } + if (!nullToAbsent || fNumber != null) { + map['f_number'] = Variable(fNumber); + } + if (!nullToAbsent || fileSize != null) { + map['file_size'] = Variable(fileSize); + } + if (!nullToAbsent || focalLength != null) { + map['focal_length'] = Variable(focalLength); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = Variable(longitude); + } + if (!nullToAbsent || iso != null) { + map['iso'] = Variable(iso); + } + if (!nullToAbsent || make != null) { + map['make'] = Variable(make); + } + if (!nullToAbsent || model != null) { + map['model'] = Variable(model); + } + if (!nullToAbsent || lens != null) { + map['lens'] = Variable(lens); + } + if (!nullToAbsent || orientation != null) { + map['orientation'] = Variable(orientation); + } + if (!nullToAbsent || timeZone != null) { + map['time_zone'] = Variable(timeZone); + } + if (!nullToAbsent || rating != null) { + map['rating'] = Variable(rating); + } + if (!nullToAbsent || projectionType != null) { + map['projection_type'] = Variable(projectionType); + } + return map; + } + + factory RemoteExifEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteExifEntityData( + assetId: serializer.fromJson(json['assetId']), + city: serializer.fromJson(json['city']), + state: serializer.fromJson(json['state']), + country: serializer.fromJson(json['country']), + dateTimeOriginal: serializer.fromJson(json['dateTimeOriginal']), + description: serializer.fromJson(json['description']), + height: serializer.fromJson(json['height']), + width: serializer.fromJson(json['width']), + exposureTime: serializer.fromJson(json['exposureTime']), + fNumber: serializer.fromJson(json['fNumber']), + fileSize: serializer.fromJson(json['fileSize']), + focalLength: serializer.fromJson(json['focalLength']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + iso: serializer.fromJson(json['iso']), + make: serializer.fromJson(json['make']), + model: serializer.fromJson(json['model']), + lens: serializer.fromJson(json['lens']), + orientation: serializer.fromJson(json['orientation']), + timeZone: serializer.fromJson(json['timeZone']), + rating: serializer.fromJson(json['rating']), + projectionType: serializer.fromJson(json['projectionType']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'city': serializer.toJson(city), + 'state': serializer.toJson(state), + 'country': serializer.toJson(country), + 'dateTimeOriginal': serializer.toJson(dateTimeOriginal), + 'description': serializer.toJson(description), + 'height': serializer.toJson(height), + 'width': serializer.toJson(width), + 'exposureTime': serializer.toJson(exposureTime), + 'fNumber': serializer.toJson(fNumber), + 'fileSize': serializer.toJson(fileSize), + 'focalLength': serializer.toJson(focalLength), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + 'iso': serializer.toJson(iso), + 'make': serializer.toJson(make), + 'model': serializer.toJson(model), + 'lens': serializer.toJson(lens), + 'orientation': serializer.toJson(orientation), + 'timeZone': serializer.toJson(timeZone), + 'rating': serializer.toJson(rating), + 'projectionType': serializer.toJson(projectionType), + }; + } + + RemoteExifEntityData copyWith({ + String? assetId, + Value city = const Value.absent(), + Value state = const Value.absent(), + Value country = const Value.absent(), + Value dateTimeOriginal = const Value.absent(), + Value description = const Value.absent(), + Value height = const Value.absent(), + Value width = const Value.absent(), + Value exposureTime = const Value.absent(), + Value fNumber = const Value.absent(), + Value fileSize = const Value.absent(), + Value focalLength = const Value.absent(), + Value latitude = const Value.absent(), + Value longitude = const Value.absent(), + Value iso = const Value.absent(), + Value make = const Value.absent(), + Value model = const Value.absent(), + Value lens = const Value.absent(), + Value orientation = const Value.absent(), + Value timeZone = const Value.absent(), + Value rating = const Value.absent(), + Value projectionType = const Value.absent(), + }) => RemoteExifEntityData( + assetId: assetId ?? this.assetId, + city: city.present ? city.value : this.city, + state: state.present ? state.value : this.state, + country: country.present ? country.value : this.country, + dateTimeOriginal: dateTimeOriginal.present + ? dateTimeOriginal.value + : this.dateTimeOriginal, + description: description.present ? description.value : this.description, + height: height.present ? height.value : this.height, + width: width.present ? width.value : this.width, + exposureTime: exposureTime.present ? exposureTime.value : this.exposureTime, + fNumber: fNumber.present ? fNumber.value : this.fNumber, + fileSize: fileSize.present ? fileSize.value : this.fileSize, + focalLength: focalLength.present ? focalLength.value : this.focalLength, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + iso: iso.present ? iso.value : this.iso, + make: make.present ? make.value : this.make, + model: model.present ? model.value : this.model, + lens: lens.present ? lens.value : this.lens, + orientation: orientation.present ? orientation.value : this.orientation, + timeZone: timeZone.present ? timeZone.value : this.timeZone, + rating: rating.present ? rating.value : this.rating, + projectionType: projectionType.present + ? projectionType.value + : this.projectionType, + ); + RemoteExifEntityData copyWithCompanion(RemoteExifEntityCompanion data) { + return RemoteExifEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + city: data.city.present ? data.city.value : this.city, + state: data.state.present ? data.state.value : this.state, + country: data.country.present ? data.country.value : this.country, + dateTimeOriginal: data.dateTimeOriginal.present + ? data.dateTimeOriginal.value + : this.dateTimeOriginal, + description: data.description.present + ? data.description.value + : this.description, + height: data.height.present ? data.height.value : this.height, + width: data.width.present ? data.width.value : this.width, + exposureTime: data.exposureTime.present + ? data.exposureTime.value + : this.exposureTime, + fNumber: data.fNumber.present ? data.fNumber.value : this.fNumber, + fileSize: data.fileSize.present ? data.fileSize.value : this.fileSize, + focalLength: data.focalLength.present + ? data.focalLength.value + : this.focalLength, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + iso: data.iso.present ? data.iso.value : this.iso, + make: data.make.present ? data.make.value : this.make, + model: data.model.present ? data.model.value : this.model, + lens: data.lens.present ? data.lens.value : this.lens, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + timeZone: data.timeZone.present ? data.timeZone.value : this.timeZone, + rating: data.rating.present ? data.rating.value : this.rating, + projectionType: data.projectionType.present + ? data.projectionType.value + : this.projectionType, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityData(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hashAll([ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteExifEntityData && + other.assetId == this.assetId && + other.city == this.city && + other.state == this.state && + other.country == this.country && + other.dateTimeOriginal == this.dateTimeOriginal && + other.description == this.description && + other.height == this.height && + other.width == this.width && + other.exposureTime == this.exposureTime && + other.fNumber == this.fNumber && + other.fileSize == this.fileSize && + other.focalLength == this.focalLength && + other.latitude == this.latitude && + other.longitude == this.longitude && + other.iso == this.iso && + other.make == this.make && + other.model == this.model && + other.lens == this.lens && + other.orientation == this.orientation && + other.timeZone == this.timeZone && + other.rating == this.rating && + other.projectionType == this.projectionType); +} + +class RemoteExifEntityCompanion extends UpdateCompanion { + final Value assetId; + final Value city; + final Value state; + final Value country; + final Value dateTimeOriginal; + final Value description; + final Value height; + final Value width; + final Value exposureTime; + final Value fNumber; + final Value fileSize; + final Value focalLength; + final Value latitude; + final Value longitude; + final Value iso; + final Value make; + final Value model; + final Value lens; + final Value orientation; + final Value timeZone; + final Value rating; + final Value projectionType; + const RemoteExifEntityCompanion({ + this.assetId = const Value.absent(), + this.city = const Value.absent(), + this.state = const Value.absent(), + this.country = const Value.absent(), + this.dateTimeOriginal = const Value.absent(), + this.description = const Value.absent(), + this.height = const Value.absent(), + this.width = const Value.absent(), + this.exposureTime = const Value.absent(), + this.fNumber = const Value.absent(), + this.fileSize = const Value.absent(), + this.focalLength = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.iso = const Value.absent(), + this.make = const Value.absent(), + this.model = const Value.absent(), + this.lens = const Value.absent(), + this.orientation = const Value.absent(), + this.timeZone = const Value.absent(), + this.rating = const Value.absent(), + this.projectionType = const Value.absent(), + }); + RemoteExifEntityCompanion.insert({ + required String assetId, + this.city = const Value.absent(), + this.state = const Value.absent(), + this.country = const Value.absent(), + this.dateTimeOriginal = const Value.absent(), + this.description = const Value.absent(), + this.height = const Value.absent(), + this.width = const Value.absent(), + this.exposureTime = const Value.absent(), + this.fNumber = const Value.absent(), + this.fileSize = const Value.absent(), + this.focalLength = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.iso = const Value.absent(), + this.make = const Value.absent(), + this.model = const Value.absent(), + this.lens = const Value.absent(), + this.orientation = const Value.absent(), + this.timeZone = const Value.absent(), + this.rating = const Value.absent(), + this.projectionType = const Value.absent(), + }) : assetId = Value(assetId); + static Insertable custom({ + Expression? assetId, + Expression? city, + Expression? state, + Expression? country, + Expression? dateTimeOriginal, + Expression? description, + Expression? height, + Expression? width, + Expression? exposureTime, + Expression? fNumber, + Expression? fileSize, + Expression? focalLength, + Expression? latitude, + Expression? longitude, + Expression? iso, + Expression? make, + Expression? model, + Expression? lens, + Expression? orientation, + Expression? timeZone, + Expression? rating, + Expression? projectionType, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (city != null) 'city': city, + if (state != null) 'state': state, + if (country != null) 'country': country, + if (dateTimeOriginal != null) 'date_time_original': dateTimeOriginal, + if (description != null) 'description': description, + if (height != null) 'height': height, + if (width != null) 'width': width, + if (exposureTime != null) 'exposure_time': exposureTime, + if (fNumber != null) 'f_number': fNumber, + if (fileSize != null) 'file_size': fileSize, + if (focalLength != null) 'focal_length': focalLength, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + if (iso != null) 'iso': iso, + if (make != null) 'make': make, + if (model != null) 'model': model, + if (lens != null) 'lens': lens, + if (orientation != null) 'orientation': orientation, + if (timeZone != null) 'time_zone': timeZone, + if (rating != null) 'rating': rating, + if (projectionType != null) 'projection_type': projectionType, + }); + } + + RemoteExifEntityCompanion copyWith({ + Value? assetId, + Value? city, + Value? state, + Value? country, + Value? dateTimeOriginal, + Value? description, + Value? height, + Value? width, + Value? exposureTime, + Value? fNumber, + Value? fileSize, + Value? focalLength, + Value? latitude, + Value? longitude, + Value? iso, + Value? make, + Value? model, + Value? lens, + Value? orientation, + Value? timeZone, + Value? rating, + Value? projectionType, + }) { + return RemoteExifEntityCompanion( + assetId: assetId ?? this.assetId, + city: city ?? this.city, + state: state ?? this.state, + country: country ?? this.country, + dateTimeOriginal: dateTimeOriginal ?? this.dateTimeOriginal, + description: description ?? this.description, + height: height ?? this.height, + width: width ?? this.width, + exposureTime: exposureTime ?? this.exposureTime, + fNumber: fNumber ?? this.fNumber, + fileSize: fileSize ?? this.fileSize, + focalLength: focalLength ?? this.focalLength, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + iso: iso ?? this.iso, + make: make ?? this.make, + model: model ?? this.model, + lens: lens ?? this.lens, + orientation: orientation ?? this.orientation, + timeZone: timeZone ?? this.timeZone, + rating: rating ?? this.rating, + projectionType: projectionType ?? this.projectionType, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (city.present) { + map['city'] = Variable(city.value); + } + if (state.present) { + map['state'] = Variable(state.value); + } + if (country.present) { + map['country'] = Variable(country.value); + } + if (dateTimeOriginal.present) { + map['date_time_original'] = Variable(dateTimeOriginal.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (exposureTime.present) { + map['exposure_time'] = Variable(exposureTime.value); + } + if (fNumber.present) { + map['f_number'] = Variable(fNumber.value); + } + if (fileSize.present) { + map['file_size'] = Variable(fileSize.value); + } + if (focalLength.present) { + map['focal_length'] = Variable(focalLength.value); + } + if (latitude.present) { + map['latitude'] = Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = Variable(longitude.value); + } + if (iso.present) { + map['iso'] = Variable(iso.value); + } + if (make.present) { + map['make'] = Variable(make.value); + } + if (model.present) { + map['model'] = Variable(model.value); + } + if (lens.present) { + map['lens'] = Variable(lens.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + if (timeZone.present) { + map['time_zone'] = Variable(timeZone.value); + } + if (rating.present) { + map['rating'] = Variable(rating.value); + } + if (projectionType.present) { + map['projection_type'] = Variable(projectionType.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES remote_asset_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES remote_album_entity(id)ON DELETE CASCADE', + ); + @override + List get $columns => [assetId, albumId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_asset_entity'; + @override + Set get $primaryKey => {assetId, albumId}; + @override + RemoteAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + ); + } + + @override + RemoteAlbumAssetEntity createAlias(String alias) { + return RemoteAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const [ + 'PRIMARY KEY(asset_id, album_id)', + ]; + @override + bool get dontWriteConstraints => true; +} + +class RemoteAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + const RemoteAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['album_id'] = Variable(albumId); + return map; + } + + factory RemoteAlbumAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + }; + } + + RemoteAlbumAssetEntityData copyWith({String? assetId, String? albumId}) => + RemoteAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + RemoteAlbumAssetEntityData copyWithCompanion( + RemoteAlbumAssetEntityCompanion data, + ) { + return RemoteAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId); +} + +class RemoteAlbumAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value albumId; + const RemoteAlbumAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.albumId = const Value.absent(), + }); + RemoteAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + }) : assetId = Value(assetId), + albumId = Value(albumId); + static Insertable custom({ + Expression? assetId, + Expression? albumId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + }); + } + + RemoteAlbumAssetEntityCompanion copyWith({ + Value? assetId, + Value? albumId, + }) { + return RemoteAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumUserEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumUserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES remote_album_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL REFERENCES user_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn role = GeneratedColumn( + 'role', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + @override + List get $columns => [albumId, userId, role]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_user_entity'; + @override + Set get $primaryKey => {albumId, userId}; + @override + RemoteAlbumUserEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumUserEntityData( + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + role: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}role'], + )!, + ); + } + + @override + RemoteAlbumUserEntity createAlias(String alias) { + return RemoteAlbumUserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const [ + 'PRIMARY KEY(album_id, user_id)', + ]; + @override + bool get dontWriteConstraints => true; +} + +class RemoteAlbumUserEntityData extends DataClass + implements Insertable { + final String albumId; + final String userId; + final int role; + const RemoteAlbumUserEntityData({ + required this.albumId, + required this.userId, + required this.role, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['album_id'] = Variable(albumId); + map['user_id'] = Variable(userId); + map['role'] = Variable(role); + return map; + } + + factory RemoteAlbumUserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumUserEntityData( + albumId: serializer.fromJson(json['albumId']), + userId: serializer.fromJson(json['userId']), + role: serializer.fromJson(json['role']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'albumId': serializer.toJson(albumId), + 'userId': serializer.toJson(userId), + 'role': serializer.toJson(role), + }; + } + + RemoteAlbumUserEntityData copyWith({ + String? albumId, + String? userId, + int? role, + }) => RemoteAlbumUserEntityData( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + RemoteAlbumUserEntityData copyWithCompanion( + RemoteAlbumUserEntityCompanion data, + ) { + return RemoteAlbumUserEntityData( + albumId: data.albumId.present ? data.albumId.value : this.albumId, + userId: data.userId.present ? data.userId.value : this.userId, + role: data.role.present ? data.role.value : this.role, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityData(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(albumId, userId, role); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumUserEntityData && + other.albumId == this.albumId && + other.userId == this.userId && + other.role == this.role); +} + +class RemoteAlbumUserEntityCompanion + extends UpdateCompanion { + final Value albumId; + final Value userId; + final Value role; + const RemoteAlbumUserEntityCompanion({ + this.albumId = const Value.absent(), + this.userId = const Value.absent(), + this.role = const Value.absent(), + }); + RemoteAlbumUserEntityCompanion.insert({ + required String albumId, + required String userId, + required int role, + }) : albumId = Value(albumId), + userId = Value(userId), + role = Value(role); + static Insertable custom({ + Expression? albumId, + Expression? userId, + Expression? role, + }) { + return RawValuesInsertable({ + if (albumId != null) 'album_id': albumId, + if (userId != null) 'user_id': userId, + if (role != null) 'role': role, + }); + } + + RemoteAlbumUserEntityCompanion copyWith({ + Value? albumId, + Value? userId, + Value? role, + }) { + return RemoteAlbumUserEntityCompanion( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (role.present) { + map['role'] = Variable(role.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityCompanion(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } +} + +class RemoteAssetCloudIdEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAssetCloudIdEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES remote_asset_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn cloudId = GeneratedColumn( + 'cloud_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn adjustmentTime = GeneratedColumn( + 'adjustment_time', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + @override + List get $columns => [ + assetId, + cloudId, + createdAt, + adjustmentTime, + latitude, + longitude, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_asset_cloud_id_entity'; + @override + Set get $primaryKey => {assetId}; + @override + RemoteAssetCloudIdEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAssetCloudIdEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + cloudId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}cloud_id'], + ), + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}created_at'], + ), + adjustmentTime: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}adjustment_time'], + ), + latitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + ); + } + + @override + RemoteAssetCloudIdEntity createAlias(String alias) { + return RemoteAssetCloudIdEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(asset_id)']; + @override + bool get dontWriteConstraints => true; +} + +class RemoteAssetCloudIdEntityData extends DataClass + implements Insertable { + final String assetId; + final String? cloudId; + final String? createdAt; + final String? adjustmentTime; + final double? latitude; + final double? longitude; + const RemoteAssetCloudIdEntityData({ + required this.assetId, + this.cloudId, + this.createdAt, + this.adjustmentTime, + this.latitude, + this.longitude, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || cloudId != null) { + map['cloud_id'] = Variable(cloudId); + } + if (!nullToAbsent || createdAt != null) { + map['created_at'] = Variable(createdAt); + } + if (!nullToAbsent || adjustmentTime != null) { + map['adjustment_time'] = Variable(adjustmentTime); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = Variable(longitude); + } + return map; + } + + factory RemoteAssetCloudIdEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAssetCloudIdEntityData( + assetId: serializer.fromJson(json['assetId']), + cloudId: serializer.fromJson(json['cloudId']), + createdAt: serializer.fromJson(json['createdAt']), + adjustmentTime: serializer.fromJson(json['adjustmentTime']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'cloudId': serializer.toJson(cloudId), + 'createdAt': serializer.toJson(createdAt), + 'adjustmentTime': serializer.toJson(adjustmentTime), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + }; + } + + RemoteAssetCloudIdEntityData copyWith({ + String? assetId, + Value cloudId = const Value.absent(), + Value createdAt = const Value.absent(), + Value adjustmentTime = const Value.absent(), + Value latitude = const Value.absent(), + Value longitude = const Value.absent(), + }) => RemoteAssetCloudIdEntityData( + assetId: assetId ?? this.assetId, + cloudId: cloudId.present ? cloudId.value : this.cloudId, + createdAt: createdAt.present ? createdAt.value : this.createdAt, + adjustmentTime: adjustmentTime.present + ? adjustmentTime.value + : this.adjustmentTime, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + ); + RemoteAssetCloudIdEntityData copyWithCompanion( + RemoteAssetCloudIdEntityCompanion data, + ) { + return RemoteAssetCloudIdEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + cloudId: data.cloudId.present ? data.cloudId.value : this.cloudId, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + adjustmentTime: data.adjustmentTime.present + ? data.adjustmentTime.value + : this.adjustmentTime, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAssetCloudIdEntityData(') + ..write('assetId: $assetId, ') + ..write('cloudId: $cloudId, ') + ..write('createdAt: $createdAt, ') + ..write('adjustmentTime: $adjustmentTime, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + assetId, + cloudId, + createdAt, + adjustmentTime, + latitude, + longitude, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAssetCloudIdEntityData && + other.assetId == this.assetId && + other.cloudId == this.cloudId && + other.createdAt == this.createdAt && + other.adjustmentTime == this.adjustmentTime && + other.latitude == this.latitude && + other.longitude == this.longitude); +} + +class RemoteAssetCloudIdEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value cloudId; + final Value createdAt; + final Value adjustmentTime; + final Value latitude; + final Value longitude; + const RemoteAssetCloudIdEntityCompanion({ + this.assetId = const Value.absent(), + this.cloudId = const Value.absent(), + this.createdAt = const Value.absent(), + this.adjustmentTime = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + }); + RemoteAssetCloudIdEntityCompanion.insert({ + required String assetId, + this.cloudId = const Value.absent(), + this.createdAt = const Value.absent(), + this.adjustmentTime = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + }) : assetId = Value(assetId); + static Insertable custom({ + Expression? assetId, + Expression? cloudId, + Expression? createdAt, + Expression? adjustmentTime, + Expression? latitude, + Expression? longitude, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (cloudId != null) 'cloud_id': cloudId, + if (createdAt != null) 'created_at': createdAt, + if (adjustmentTime != null) 'adjustment_time': adjustmentTime, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + }); + } + + RemoteAssetCloudIdEntityCompanion copyWith({ + Value? assetId, + Value? cloudId, + Value? createdAt, + Value? adjustmentTime, + Value? latitude, + Value? longitude, + }) { + return RemoteAssetCloudIdEntityCompanion( + assetId: assetId ?? this.assetId, + cloudId: cloudId ?? this.cloudId, + createdAt: createdAt ?? this.createdAt, + adjustmentTime: adjustmentTime ?? this.adjustmentTime, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (cloudId.present) { + map['cloud_id'] = Variable(cloudId.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (adjustmentTime.present) { + map['adjustment_time'] = Variable(adjustmentTime.value); + } + if (latitude.present) { + map['latitude'] = Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = Variable(longitude.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAssetCloudIdEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('cloudId: $cloudId, ') + ..write('createdAt: $createdAt, ') + ..write('adjustmentTime: $adjustmentTime, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude') + ..write(')')) + .toString(); + } +} + +class MemoryEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MemoryEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL REFERENCES user_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn data = GeneratedColumn( + 'data', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn isSaved = GeneratedColumn( + 'is_saved', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0 CHECK (is_saved IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn memoryAt = GeneratedColumn( + 'memory_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn seenAt = GeneratedColumn( + 'seen_at', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn showAt = GeneratedColumn( + 'show_at', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn hideAt = GeneratedColumn( + 'hide_at', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_entity'; + @override + Set get $primaryKey => {id}; + @override + MemoryEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MemoryEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}deleted_at'], + ), + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + data: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}data'], + )!, + isSaved: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_saved'], + )!, + memoryAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}memory_at'], + )!, + seenAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}seen_at'], + ), + showAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}show_at'], + ), + hideAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}hide_at'], + ), + ); + } + + @override + MemoryEntity createAlias(String alias) { + return MemoryEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class MemoryEntityData extends DataClass + implements Insertable { + final String id; + final String createdAt; + final String updatedAt; + final String? deletedAt; + final String ownerId; + final int type; + final String data; + final int isSaved; + final String memoryAt; + final String? seenAt; + final String? showAt; + final String? hideAt; + const MemoryEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + this.deletedAt, + required this.ownerId, + required this.type, + required this.data, + required this.isSaved, + required this.memoryAt, + this.seenAt, + this.showAt, + this.hideAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + map['owner_id'] = Variable(ownerId); + map['type'] = Variable(type); + map['data'] = Variable(data); + map['is_saved'] = Variable(isSaved); + map['memory_at'] = Variable(memoryAt); + if (!nullToAbsent || seenAt != null) { + map['seen_at'] = Variable(seenAt); + } + if (!nullToAbsent || showAt != null) { + map['show_at'] = Variable(showAt); + } + if (!nullToAbsent || hideAt != null) { + map['hide_at'] = Variable(hideAt); + } + return map; + } + + factory MemoryEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MemoryEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + deletedAt: serializer.fromJson(json['deletedAt']), + ownerId: serializer.fromJson(json['ownerId']), + type: serializer.fromJson(json['type']), + data: serializer.fromJson(json['data']), + isSaved: serializer.fromJson(json['isSaved']), + memoryAt: serializer.fromJson(json['memoryAt']), + seenAt: serializer.fromJson(json['seenAt']), + showAt: serializer.fromJson(json['showAt']), + hideAt: serializer.fromJson(json['hideAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'deletedAt': serializer.toJson(deletedAt), + 'ownerId': serializer.toJson(ownerId), + 'type': serializer.toJson(type), + 'data': serializer.toJson(data), + 'isSaved': serializer.toJson(isSaved), + 'memoryAt': serializer.toJson(memoryAt), + 'seenAt': serializer.toJson(seenAt), + 'showAt': serializer.toJson(showAt), + 'hideAt': serializer.toJson(hideAt), + }; + } + + MemoryEntityData copyWith({ + String? id, + String? createdAt, + String? updatedAt, + Value deletedAt = const Value.absent(), + String? ownerId, + int? type, + String? data, + int? isSaved, + String? memoryAt, + Value seenAt = const Value.absent(), + Value showAt = const Value.absent(), + Value hideAt = const Value.absent(), + }) => MemoryEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt.present ? seenAt.value : this.seenAt, + showAt: showAt.present ? showAt.value : this.showAt, + hideAt: hideAt.present ? hideAt.value : this.hideAt, + ); + MemoryEntityData copyWithCompanion(MemoryEntityCompanion data) { + return MemoryEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + type: data.type.present ? data.type.value : this.type, + data: data.data.present ? data.data.value : this.data, + isSaved: data.isSaved.present ? data.isSaved.value : this.isSaved, + memoryAt: data.memoryAt.present ? data.memoryAt.value : this.memoryAt, + seenAt: data.seenAt.present ? data.seenAt.value : this.seenAt, + showAt: data.showAt.present ? data.showAt.value : this.showAt, + hideAt: data.hideAt.present ? data.hideAt.value : this.hideAt, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MemoryEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.deletedAt == this.deletedAt && + other.ownerId == this.ownerId && + other.type == this.type && + other.data == this.data && + other.isSaved == this.isSaved && + other.memoryAt == this.memoryAt && + other.seenAt == this.seenAt && + other.showAt == this.showAt && + other.hideAt == this.hideAt); +} + +class MemoryEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value deletedAt; + final Value ownerId; + final Value type; + final Value data; + final Value isSaved; + final Value memoryAt; + final Value seenAt; + final Value showAt; + final Value hideAt; + const MemoryEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.deletedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.type = const Value.absent(), + this.data = const Value.absent(), + this.isSaved = const Value.absent(), + this.memoryAt = const Value.absent(), + this.seenAt = const Value.absent(), + this.showAt = const Value.absent(), + this.hideAt = const Value.absent(), + }); + MemoryEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.deletedAt = const Value.absent(), + required String ownerId, + required int type, + required String data, + this.isSaved = const Value.absent(), + required String memoryAt, + this.seenAt = const Value.absent(), + this.showAt = const Value.absent(), + this.hideAt = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + type = Value(type), + data = Value(data), + memoryAt = Value(memoryAt); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? deletedAt, + Expression? ownerId, + Expression? type, + Expression? data, + Expression? isSaved, + Expression? memoryAt, + Expression? seenAt, + Expression? showAt, + Expression? hideAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (deletedAt != null) 'deleted_at': deletedAt, + if (ownerId != null) 'owner_id': ownerId, + if (type != null) 'type': type, + if (data != null) 'data': data, + if (isSaved != null) 'is_saved': isSaved, + if (memoryAt != null) 'memory_at': memoryAt, + if (seenAt != null) 'seen_at': seenAt, + if (showAt != null) 'show_at': showAt, + if (hideAt != null) 'hide_at': hideAt, + }); + } + + MemoryEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? deletedAt, + Value? ownerId, + Value? type, + Value? data, + Value? isSaved, + Value? memoryAt, + Value? seenAt, + Value? showAt, + Value? hideAt, + }) { + return MemoryEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt ?? this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt ?? this.seenAt, + showAt: showAt ?? this.showAt, + hideAt: hideAt ?? this.hideAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (data.present) { + map['data'] = Variable(data.value); + } + if (isSaved.present) { + map['is_saved'] = Variable(isSaved.value); + } + if (memoryAt.present) { + map['memory_at'] = Variable(memoryAt.value); + } + if (seenAt.present) { + map['seen_at'] = Variable(seenAt.value); + } + if (showAt.present) { + map['show_at'] = Variable(showAt.value); + } + if (hideAt.present) { + map['hide_at'] = Variable(hideAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } +} + +class MemoryAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MemoryAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES remote_asset_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn memoryId = GeneratedColumn( + 'memory_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES memory_entity(id)ON DELETE CASCADE', + ); + @override + List get $columns => [assetId, memoryId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_asset_entity'; + @override + Set get $primaryKey => {assetId, memoryId}; + @override + MemoryAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MemoryAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + memoryId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}memory_id'], + )!, + ); + } + + @override + MemoryAssetEntity createAlias(String alias) { + return MemoryAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const [ + 'PRIMARY KEY(asset_id, memory_id)', + ]; + @override + bool get dontWriteConstraints => true; +} + +class MemoryAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String memoryId; + const MemoryAssetEntityData({required this.assetId, required this.memoryId}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['memory_id'] = Variable(memoryId); + return map; + } + + factory MemoryAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MemoryAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + memoryId: serializer.fromJson(json['memoryId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'memoryId': serializer.toJson(memoryId), + }; + } + + MemoryAssetEntityData copyWith({String? assetId, String? memoryId}) => + MemoryAssetEntityData( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + MemoryAssetEntityData copyWithCompanion(MemoryAssetEntityCompanion data) { + return MemoryAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + memoryId: data.memoryId.present ? data.memoryId.value : this.memoryId, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, memoryId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MemoryAssetEntityData && + other.assetId == this.assetId && + other.memoryId == this.memoryId); +} + +class MemoryAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value memoryId; + const MemoryAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.memoryId = const Value.absent(), + }); + MemoryAssetEntityCompanion.insert({ + required String assetId, + required String memoryId, + }) : assetId = Value(assetId), + memoryId = Value(memoryId); + static Insertable custom({ + Expression? assetId, + Expression? memoryId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (memoryId != null) 'memory_id': memoryId, + }); + } + + MemoryAssetEntityCompanion copyWith({ + Value? assetId, + Value? memoryId, + }) { + return MemoryAssetEntityCompanion( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (memoryId.present) { + map['memory_id'] = Variable(memoryId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } +} + +class PersonEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PersonEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL REFERENCES user_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn faceAssetId = GeneratedColumn( + 'face_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL CHECK (is_favorite IN (0, 1))', + ); + late final GeneratedColumn isHidden = GeneratedColumn( + 'is_hidden', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL CHECK (is_hidden IN (0, 1))', + ); + late final GeneratedColumn color = GeneratedColumn( + 'color', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn birthDate = GeneratedColumn( + 'birth_date', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + isFavorite, + isHidden, + color, + birthDate, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'person_entity'; + @override + Set get $primaryKey => {id}; + @override + PersonEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PersonEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + faceAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}face_asset_id'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_favorite'], + )!, + isHidden: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_hidden'], + )!, + color: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}color'], + ), + birthDate: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}birth_date'], + ), + ); + } + + @override + PersonEntity createAlias(String alias) { + return PersonEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class PersonEntityData extends DataClass + implements Insertable { + final String id; + final String createdAt; + final String updatedAt; + final String ownerId; + final String name; + final String? faceAssetId; + final int isFavorite; + final int isHidden; + final String? color; + final String? birthDate; + const PersonEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.name, + this.faceAssetId, + required this.isFavorite, + required this.isHidden, + this.color, + this.birthDate, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + map['name'] = Variable(name); + if (!nullToAbsent || faceAssetId != null) { + map['face_asset_id'] = Variable(faceAssetId); + } + map['is_favorite'] = Variable(isFavorite); + map['is_hidden'] = Variable(isHidden); + if (!nullToAbsent || color != null) { + map['color'] = Variable(color); + } + if (!nullToAbsent || birthDate != null) { + map['birth_date'] = Variable(birthDate); + } + return map; + } + + factory PersonEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PersonEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + name: serializer.fromJson(json['name']), + faceAssetId: serializer.fromJson(json['faceAssetId']), + isFavorite: serializer.fromJson(json['isFavorite']), + isHidden: serializer.fromJson(json['isHidden']), + color: serializer.fromJson(json['color']), + birthDate: serializer.fromJson(json['birthDate']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'name': serializer.toJson(name), + 'faceAssetId': serializer.toJson(faceAssetId), + 'isFavorite': serializer.toJson(isFavorite), + 'isHidden': serializer.toJson(isHidden), + 'color': serializer.toJson(color), + 'birthDate': serializer.toJson(birthDate), + }; + } + + PersonEntityData copyWith({ + String? id, + String? createdAt, + String? updatedAt, + String? ownerId, + String? name, + Value faceAssetId = const Value.absent(), + int? isFavorite, + int? isHidden, + Value color = const Value.absent(), + Value birthDate = const Value.absent(), + }) => PersonEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId.present ? faceAssetId.value : this.faceAssetId, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color.present ? color.value : this.color, + birthDate: birthDate.present ? birthDate.value : this.birthDate, + ); + PersonEntityData copyWithCompanion(PersonEntityCompanion data) { + return PersonEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + name: data.name.present ? data.name.value : this.name, + faceAssetId: data.faceAssetId.present + ? data.faceAssetId.value + : this.faceAssetId, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + isHidden: data.isHidden.present ? data.isHidden.value : this.isHidden, + color: data.color.present ? data.color.value : this.color, + birthDate: data.birthDate.present ? data.birthDate.value : this.birthDate, + ); + } + + @override + String toString() { + return (StringBuffer('PersonEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + isFavorite, + isHidden, + color, + birthDate, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PersonEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.name == this.name && + other.faceAssetId == this.faceAssetId && + other.isFavorite == this.isFavorite && + other.isHidden == this.isHidden && + other.color == this.color && + other.birthDate == this.birthDate); +} + +class PersonEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value name; + final Value faceAssetId; + final Value isFavorite; + final Value isHidden; + final Value color; + final Value birthDate; + const PersonEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.name = const Value.absent(), + this.faceAssetId = const Value.absent(), + this.isFavorite = const Value.absent(), + this.isHidden = const Value.absent(), + this.color = const Value.absent(), + this.birthDate = const Value.absent(), + }); + PersonEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + required String name, + this.faceAssetId = const Value.absent(), + required int isFavorite, + required int isHidden, + this.color = const Value.absent(), + this.birthDate = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + name = Value(name), + isFavorite = Value(isFavorite), + isHidden = Value(isHidden); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? name, + Expression? faceAssetId, + Expression? isFavorite, + Expression? isHidden, + Expression? color, + Expression? birthDate, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (name != null) 'name': name, + if (faceAssetId != null) 'face_asset_id': faceAssetId, + if (isFavorite != null) 'is_favorite': isFavorite, + if (isHidden != null) 'is_hidden': isHidden, + if (color != null) 'color': color, + if (birthDate != null) 'birth_date': birthDate, + }); + } + + PersonEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? name, + Value? faceAssetId, + Value? isFavorite, + Value? isHidden, + Value? color, + Value? birthDate, + }) { + return PersonEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId ?? this.faceAssetId, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color ?? this.color, + birthDate: birthDate ?? this.birthDate, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (faceAssetId.present) { + map['face_asset_id'] = Variable(faceAssetId.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (isHidden.present) { + map['is_hidden'] = Variable(isHidden.value); + } + if (color.present) { + map['color'] = Variable(color.value); + } + if (birthDate.present) { + map['birth_date'] = Variable(birthDate.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PersonEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } +} + +class AssetFaceEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AssetFaceEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES remote_asset_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn personId = GeneratedColumn( + 'person_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL REFERENCES person_entity(id)ON DELETE SET NULL', + ); + late final GeneratedColumn imageWidth = GeneratedColumn( + 'image_width', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn imageHeight = GeneratedColumn( + 'image_height', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn boundingBoxX1 = GeneratedColumn( + 'bounding_box_x1', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn boundingBoxY1 = GeneratedColumn( + 'bounding_box_y1', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn boundingBoxX2 = GeneratedColumn( + 'bounding_box_x2', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn boundingBoxY2 = GeneratedColumn( + 'bounding_box_y2', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn sourceType = GeneratedColumn( + 'source_type', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn isVisible = GeneratedColumn( + 'is_visible', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 1 CHECK (is_visible IN (0, 1))', + defaultValue: const CustomExpression('1'), + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + @override + List get $columns => [ + id, + assetId, + personId, + imageWidth, + imageHeight, + boundingBoxX1, + boundingBoxY1, + boundingBoxX2, + boundingBoxY2, + sourceType, + isVisible, + deletedAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'asset_face_entity'; + @override + Set get $primaryKey => {id}; + @override + AssetFaceEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AssetFaceEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + personId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}person_id'], + ), + imageWidth: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}image_width'], + )!, + imageHeight: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}image_height'], + )!, + boundingBoxX1: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_x1'], + )!, + boundingBoxY1: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_y1'], + )!, + boundingBoxX2: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_x2'], + )!, + boundingBoxY2: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_y2'], + )!, + sourceType: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}source_type'], + )!, + isVisible: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_visible'], + )!, + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}deleted_at'], + ), + ); + } + + @override + AssetFaceEntity createAlias(String alias) { + return AssetFaceEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class AssetFaceEntityData extends DataClass + implements Insertable { + final String id; + final String assetId; + final String? personId; + final int imageWidth; + final int imageHeight; + final int boundingBoxX1; + final int boundingBoxY1; + final int boundingBoxX2; + final int boundingBoxY2; + final String sourceType; + final int isVisible; + final String? deletedAt; + const AssetFaceEntityData({ + required this.id, + required this.assetId, + this.personId, + required this.imageWidth, + required this.imageHeight, + required this.boundingBoxX1, + required this.boundingBoxY1, + required this.boundingBoxX2, + required this.boundingBoxY2, + required this.sourceType, + required this.isVisible, + this.deletedAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || personId != null) { + map['person_id'] = Variable(personId); + } + map['image_width'] = Variable(imageWidth); + map['image_height'] = Variable(imageHeight); + map['bounding_box_x1'] = Variable(boundingBoxX1); + map['bounding_box_y1'] = Variable(boundingBoxY1); + map['bounding_box_x2'] = Variable(boundingBoxX2); + map['bounding_box_y2'] = Variable(boundingBoxY2); + map['source_type'] = Variable(sourceType); + map['is_visible'] = Variable(isVisible); + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + return map; + } + + factory AssetFaceEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AssetFaceEntityData( + id: serializer.fromJson(json['id']), + assetId: serializer.fromJson(json['assetId']), + personId: serializer.fromJson(json['personId']), + imageWidth: serializer.fromJson(json['imageWidth']), + imageHeight: serializer.fromJson(json['imageHeight']), + boundingBoxX1: serializer.fromJson(json['boundingBoxX1']), + boundingBoxY1: serializer.fromJson(json['boundingBoxY1']), + boundingBoxX2: serializer.fromJson(json['boundingBoxX2']), + boundingBoxY2: serializer.fromJson(json['boundingBoxY2']), + sourceType: serializer.fromJson(json['sourceType']), + isVisible: serializer.fromJson(json['isVisible']), + deletedAt: serializer.fromJson(json['deletedAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'assetId': serializer.toJson(assetId), + 'personId': serializer.toJson(personId), + 'imageWidth': serializer.toJson(imageWidth), + 'imageHeight': serializer.toJson(imageHeight), + 'boundingBoxX1': serializer.toJson(boundingBoxX1), + 'boundingBoxY1': serializer.toJson(boundingBoxY1), + 'boundingBoxX2': serializer.toJson(boundingBoxX2), + 'boundingBoxY2': serializer.toJson(boundingBoxY2), + 'sourceType': serializer.toJson(sourceType), + 'isVisible': serializer.toJson(isVisible), + 'deletedAt': serializer.toJson(deletedAt), + }; + } + + AssetFaceEntityData copyWith({ + String? id, + String? assetId, + Value personId = const Value.absent(), + int? imageWidth, + int? imageHeight, + int? boundingBoxX1, + int? boundingBoxY1, + int? boundingBoxX2, + int? boundingBoxY2, + String? sourceType, + int? isVisible, + Value deletedAt = const Value.absent(), + }) => AssetFaceEntityData( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + personId: personId.present ? personId.value : this.personId, + imageWidth: imageWidth ?? this.imageWidth, + imageHeight: imageHeight ?? this.imageHeight, + boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1, + boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1, + boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2, + boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2, + sourceType: sourceType ?? this.sourceType, + isVisible: isVisible ?? this.isVisible, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + ); + AssetFaceEntityData copyWithCompanion(AssetFaceEntityCompanion data) { + return AssetFaceEntityData( + id: data.id.present ? data.id.value : this.id, + assetId: data.assetId.present ? data.assetId.value : this.assetId, + personId: data.personId.present ? data.personId.value : this.personId, + imageWidth: data.imageWidth.present + ? data.imageWidth.value + : this.imageWidth, + imageHeight: data.imageHeight.present + ? data.imageHeight.value + : this.imageHeight, + boundingBoxX1: data.boundingBoxX1.present + ? data.boundingBoxX1.value + : this.boundingBoxX1, + boundingBoxY1: data.boundingBoxY1.present + ? data.boundingBoxY1.value + : this.boundingBoxY1, + boundingBoxX2: data.boundingBoxX2.present + ? data.boundingBoxX2.value + : this.boundingBoxX2, + boundingBoxY2: data.boundingBoxY2.present + ? data.boundingBoxY2.value + : this.boundingBoxY2, + sourceType: data.sourceType.present + ? data.sourceType.value + : this.sourceType, + isVisible: data.isVisible.present ? data.isVisible.value : this.isVisible, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + ); + } + + @override + String toString() { + return (StringBuffer('AssetFaceEntityData(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('personId: $personId, ') + ..write('imageWidth: $imageWidth, ') + ..write('imageHeight: $imageHeight, ') + ..write('boundingBoxX1: $boundingBoxX1, ') + ..write('boundingBoxY1: $boundingBoxY1, ') + ..write('boundingBoxX2: $boundingBoxX2, ') + ..write('boundingBoxY2: $boundingBoxY2, ') + ..write('sourceType: $sourceType, ') + ..write('isVisible: $isVisible, ') + ..write('deletedAt: $deletedAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + assetId, + personId, + imageWidth, + imageHeight, + boundingBoxX1, + boundingBoxY1, + boundingBoxX2, + boundingBoxY2, + sourceType, + isVisible, + deletedAt, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AssetFaceEntityData && + other.id == this.id && + other.assetId == this.assetId && + other.personId == this.personId && + other.imageWidth == this.imageWidth && + other.imageHeight == this.imageHeight && + other.boundingBoxX1 == this.boundingBoxX1 && + other.boundingBoxY1 == this.boundingBoxY1 && + other.boundingBoxX2 == this.boundingBoxX2 && + other.boundingBoxY2 == this.boundingBoxY2 && + other.sourceType == this.sourceType && + other.isVisible == this.isVisible && + other.deletedAt == this.deletedAt); +} + +class AssetFaceEntityCompanion extends UpdateCompanion { + final Value id; + final Value assetId; + final Value personId; + final Value imageWidth; + final Value imageHeight; + final Value boundingBoxX1; + final Value boundingBoxY1; + final Value boundingBoxX2; + final Value boundingBoxY2; + final Value sourceType; + final Value isVisible; + final Value deletedAt; + const AssetFaceEntityCompanion({ + this.id = const Value.absent(), + this.assetId = const Value.absent(), + this.personId = const Value.absent(), + this.imageWidth = const Value.absent(), + this.imageHeight = const Value.absent(), + this.boundingBoxX1 = const Value.absent(), + this.boundingBoxY1 = const Value.absent(), + this.boundingBoxX2 = const Value.absent(), + this.boundingBoxY2 = const Value.absent(), + this.sourceType = const Value.absent(), + this.isVisible = const Value.absent(), + this.deletedAt = const Value.absent(), + }); + AssetFaceEntityCompanion.insert({ + required String id, + required String assetId, + this.personId = const Value.absent(), + required int imageWidth, + required int imageHeight, + required int boundingBoxX1, + required int boundingBoxY1, + required int boundingBoxX2, + required int boundingBoxY2, + required String sourceType, + this.isVisible = const Value.absent(), + this.deletedAt = const Value.absent(), + }) : id = Value(id), + assetId = Value(assetId), + imageWidth = Value(imageWidth), + imageHeight = Value(imageHeight), + boundingBoxX1 = Value(boundingBoxX1), + boundingBoxY1 = Value(boundingBoxY1), + boundingBoxX2 = Value(boundingBoxX2), + boundingBoxY2 = Value(boundingBoxY2), + sourceType = Value(sourceType); + static Insertable custom({ + Expression? id, + Expression? assetId, + Expression? personId, + Expression? imageWidth, + Expression? imageHeight, + Expression? boundingBoxX1, + Expression? boundingBoxY1, + Expression? boundingBoxX2, + Expression? boundingBoxY2, + Expression? sourceType, + Expression? isVisible, + Expression? deletedAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (assetId != null) 'asset_id': assetId, + if (personId != null) 'person_id': personId, + if (imageWidth != null) 'image_width': imageWidth, + if (imageHeight != null) 'image_height': imageHeight, + if (boundingBoxX1 != null) 'bounding_box_x1': boundingBoxX1, + if (boundingBoxY1 != null) 'bounding_box_y1': boundingBoxY1, + if (boundingBoxX2 != null) 'bounding_box_x2': boundingBoxX2, + if (boundingBoxY2 != null) 'bounding_box_y2': boundingBoxY2, + if (sourceType != null) 'source_type': sourceType, + if (isVisible != null) 'is_visible': isVisible, + if (deletedAt != null) 'deleted_at': deletedAt, + }); + } + + AssetFaceEntityCompanion copyWith({ + Value? id, + Value? assetId, + Value? personId, + Value? imageWidth, + Value? imageHeight, + Value? boundingBoxX1, + Value? boundingBoxY1, + Value? boundingBoxX2, + Value? boundingBoxY2, + Value? sourceType, + Value? isVisible, + Value? deletedAt, + }) { + return AssetFaceEntityCompanion( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + personId: personId ?? this.personId, + imageWidth: imageWidth ?? this.imageWidth, + imageHeight: imageHeight ?? this.imageHeight, + boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1, + boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1, + boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2, + boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2, + sourceType: sourceType ?? this.sourceType, + isVisible: isVisible ?? this.isVisible, + deletedAt: deletedAt ?? this.deletedAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (personId.present) { + map['person_id'] = Variable(personId.value); + } + if (imageWidth.present) { + map['image_width'] = Variable(imageWidth.value); + } + if (imageHeight.present) { + map['image_height'] = Variable(imageHeight.value); + } + if (boundingBoxX1.present) { + map['bounding_box_x1'] = Variable(boundingBoxX1.value); + } + if (boundingBoxY1.present) { + map['bounding_box_y1'] = Variable(boundingBoxY1.value); + } + if (boundingBoxX2.present) { + map['bounding_box_x2'] = Variable(boundingBoxX2.value); + } + if (boundingBoxY2.present) { + map['bounding_box_y2'] = Variable(boundingBoxY2.value); + } + if (sourceType.present) { + map['source_type'] = Variable(sourceType.value); + } + if (isVisible.present) { + map['is_visible'] = Variable(isVisible.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AssetFaceEntityCompanion(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('personId: $personId, ') + ..write('imageWidth: $imageWidth, ') + ..write('imageHeight: $imageHeight, ') + ..write('boundingBoxX1: $boundingBoxX1, ') + ..write('boundingBoxY1: $boundingBoxY1, ') + ..write('boundingBoxX2: $boundingBoxX2, ') + ..write('boundingBoxY2: $boundingBoxY2, ') + ..write('sourceType: $sourceType, ') + ..write('isVisible: $isVisible, ') + ..write('deletedAt: $deletedAt') + ..write(')')) + .toString(); + } +} + +class StoreEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + StoreEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn stringValue = GeneratedColumn( + 'string_value', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn intValue = GeneratedColumn( + 'int_value', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + @override + List get $columns => [id, stringValue, intValue]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'store_entity'; + @override + Set get $primaryKey => {id}; + @override + StoreEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return StoreEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}id'], + )!, + stringValue: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}string_value'], + ), + intValue: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}int_value'], + ), + ); + } + + @override + StoreEntity createAlias(String alias) { + return StoreEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class StoreEntityData extends DataClass implements Insertable { + final int id; + final String? stringValue; + final int? intValue; + const StoreEntityData({required this.id, this.stringValue, this.intValue}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + if (!nullToAbsent || stringValue != null) { + map['string_value'] = Variable(stringValue); + } + if (!nullToAbsent || intValue != null) { + map['int_value'] = Variable(intValue); + } + return map; + } + + factory StoreEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return StoreEntityData( + id: serializer.fromJson(json['id']), + stringValue: serializer.fromJson(json['stringValue']), + intValue: serializer.fromJson(json['intValue']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'stringValue': serializer.toJson(stringValue), + 'intValue': serializer.toJson(intValue), + }; + } + + StoreEntityData copyWith({ + int? id, + Value stringValue = const Value.absent(), + Value intValue = const Value.absent(), + }) => StoreEntityData( + id: id ?? this.id, + stringValue: stringValue.present ? stringValue.value : this.stringValue, + intValue: intValue.present ? intValue.value : this.intValue, + ); + StoreEntityData copyWithCompanion(StoreEntityCompanion data) { + return StoreEntityData( + id: data.id.present ? data.id.value : this.id, + stringValue: data.stringValue.present + ? data.stringValue.value + : this.stringValue, + intValue: data.intValue.present ? data.intValue.value : this.intValue, + ); + } + + @override + String toString() { + return (StringBuffer('StoreEntityData(') + ..write('id: $id, ') + ..write('stringValue: $stringValue, ') + ..write('intValue: $intValue') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, stringValue, intValue); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is StoreEntityData && + other.id == this.id && + other.stringValue == this.stringValue && + other.intValue == this.intValue); +} + +class StoreEntityCompanion extends UpdateCompanion { + final Value id; + final Value stringValue; + final Value intValue; + const StoreEntityCompanion({ + this.id = const Value.absent(), + this.stringValue = const Value.absent(), + this.intValue = const Value.absent(), + }); + StoreEntityCompanion.insert({ + required int id, + this.stringValue = const Value.absent(), + this.intValue = const Value.absent(), + }) : id = Value(id); + static Insertable custom({ + Expression? id, + Expression? stringValue, + Expression? intValue, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (stringValue != null) 'string_value': stringValue, + if (intValue != null) 'int_value': intValue, + }); + } + + StoreEntityCompanion copyWith({ + Value? id, + Value? stringValue, + Value? intValue, + }) { + return StoreEntityCompanion( + id: id ?? this.id, + stringValue: stringValue ?? this.stringValue, + intValue: intValue ?? this.intValue, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (stringValue.present) { + map['string_value'] = Variable(stringValue.value); + } + if (intValue.present) { + map['int_value'] = Variable(intValue.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('StoreEntityCompanion(') + ..write('id: $id, ') + ..write('stringValue: $stringValue, ') + ..write('intValue: $intValue') + ..write(')')) + .toString(); + } +} + +class TrashedLocalAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + TrashedLocalAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn durationMs = GeneratedColumn( + 'duration_ms', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0 CHECK (is_favorite IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn source = GeneratedColumn( + 'source', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn playbackStyle = GeneratedColumn( + 'playback_style', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0', + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationMs, + id, + albumId, + checksum, + isFavorite, + orientation, + source, + playbackStyle, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'trashed_local_asset_entity'; + @override + Set get $primaryKey => {id, albumId}; + @override + TrashedLocalAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return TrashedLocalAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationMs: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_ms'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_favorite'], + )!, + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}orientation'], + )!, + source: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}source'], + )!, + playbackStyle: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}playback_style'], + )!, + ); + } + + @override + TrashedLocalAssetEntity createAlias(String alias) { + return TrashedLocalAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id, album_id)']; + @override + bool get dontWriteConstraints => true; +} + +class TrashedLocalAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final String createdAt; + final String updatedAt; + final int? width; + final int? height; + final int? durationMs; + final String id; + final String albumId; + final String? checksum; + final int isFavorite; + final int orientation; + final int source; + final int playbackStyle; + const TrashedLocalAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationMs, + required this.id, + required this.albumId, + this.checksum, + required this.isFavorite, + required this.orientation, + required this.source, + required this.playbackStyle, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationMs != null) { + map['duration_ms'] = Variable(durationMs); + } + map['id'] = Variable(id); + map['album_id'] = Variable(albumId); + if (!nullToAbsent || checksum != null) { + map['checksum'] = Variable(checksum); + } + map['is_favorite'] = Variable(isFavorite); + map['orientation'] = Variable(orientation); + map['source'] = Variable(source); + map['playback_style'] = Variable(playbackStyle); + return map; + } + + factory TrashedLocalAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return TrashedLocalAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationMs: serializer.fromJson(json['durationMs']), + id: serializer.fromJson(json['id']), + albumId: serializer.fromJson(json['albumId']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + orientation: serializer.fromJson(json['orientation']), + source: serializer.fromJson(json['source']), + playbackStyle: serializer.fromJson(json['playbackStyle']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationMs': serializer.toJson(durationMs), + 'id': serializer.toJson(id), + 'albumId': serializer.toJson(albumId), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'orientation': serializer.toJson(orientation), + 'source': serializer.toJson(source), + 'playbackStyle': serializer.toJson(playbackStyle), + }; + } + + TrashedLocalAssetEntityData copyWith({ + String? name, + int? type, + String? createdAt, + String? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationMs = const Value.absent(), + String? id, + String? albumId, + Value checksum = const Value.absent(), + int? isFavorite, + int? orientation, + int? source, + int? playbackStyle, + }) => TrashedLocalAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationMs: durationMs.present ? durationMs.value : this.durationMs, + id: id ?? this.id, + albumId: albumId ?? this.albumId, + checksum: checksum.present ? checksum.value : this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + source: source ?? this.source, + playbackStyle: playbackStyle ?? this.playbackStyle, + ); + TrashedLocalAssetEntityData copyWithCompanion( + TrashedLocalAssetEntityCompanion data, + ) { + return TrashedLocalAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationMs: data.durationMs.present + ? data.durationMs.value + : this.durationMs, + id: data.id.present ? data.id.value : this.id, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + source: data.source.present ? data.source.value : this.source, + playbackStyle: data.playbackStyle.present + ? data.playbackStyle.value + : this.playbackStyle, + ); + } + + @override + String toString() { + return (StringBuffer('TrashedLocalAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationMs: $durationMs, ') + ..write('id: $id, ') + ..write('albumId: $albumId, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation, ') + ..write('source: $source, ') + ..write('playbackStyle: $playbackStyle') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationMs, + id, + albumId, + checksum, + isFavorite, + orientation, + source, + playbackStyle, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is TrashedLocalAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationMs == this.durationMs && + other.id == this.id && + other.albumId == this.albumId && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.orientation == this.orientation && + other.source == this.source && + other.playbackStyle == this.playbackStyle); +} + +class TrashedLocalAssetEntityCompanion + extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationMs; + final Value id; + final Value albumId; + final Value checksum; + final Value isFavorite; + final Value orientation; + final Value source; + final Value playbackStyle; + const TrashedLocalAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationMs = const Value.absent(), + this.id = const Value.absent(), + this.albumId = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + this.source = const Value.absent(), + this.playbackStyle = const Value.absent(), + }); + TrashedLocalAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationMs = const Value.absent(), + required String id, + required String albumId, + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + required int source, + this.playbackStyle = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id), + albumId = Value(albumId), + source = Value(source); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationMs, + Expression? id, + Expression? albumId, + Expression? checksum, + Expression? isFavorite, + Expression? orientation, + Expression? source, + Expression? playbackStyle, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationMs != null) 'duration_ms': durationMs, + if (id != null) 'id': id, + if (albumId != null) 'album_id': albumId, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (orientation != null) 'orientation': orientation, + if (source != null) 'source': source, + if (playbackStyle != null) 'playback_style': playbackStyle, + }); + } + + TrashedLocalAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationMs, + Value? id, + Value? albumId, + Value? checksum, + Value? isFavorite, + Value? orientation, + Value? source, + Value? playbackStyle, + }) { + return TrashedLocalAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationMs: durationMs ?? this.durationMs, + id: id ?? this.id, + albumId: albumId ?? this.albumId, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + source: source ?? this.source, + playbackStyle: playbackStyle ?? this.playbackStyle, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationMs.present) { + map['duration_ms'] = Variable(durationMs.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + if (source.present) { + map['source'] = Variable(source.value); + } + if (playbackStyle.present) { + map['playback_style'] = Variable(playbackStyle.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('TrashedLocalAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationMs: $durationMs, ') + ..write('id: $id, ') + ..write('albumId: $albumId, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation, ') + ..write('source: $source, ') + ..write('playbackStyle: $playbackStyle') + ..write(')')) + .toString(); + } +} + +class AssetEditEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AssetEditEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES remote_asset_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn action = GeneratedColumn( + 'action', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn parameters = + GeneratedColumn( + 'parameters', + aliasedName, + false, + type: DriftSqlType.blob, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn sequence = GeneratedColumn( + 'sequence', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + @override + List get $columns => [ + id, + assetId, + action, + parameters, + sequence, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'asset_edit_entity'; + @override + Set get $primaryKey => {id}; + @override + AssetEditEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AssetEditEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + action: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}action'], + )!, + parameters: attachedDatabase.typeMapping.read( + DriftSqlType.blob, + data['${effectivePrefix}parameters'], + )!, + sequence: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}sequence'], + )!, + ); + } + + @override + AssetEditEntity createAlias(String alias) { + return AssetEditEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class AssetEditEntityData extends DataClass + implements Insertable { + final String id; + final String assetId; + final int action; + final i2.Uint8List parameters; + final int sequence; + const AssetEditEntityData({ + required this.id, + required this.assetId, + required this.action, + required this.parameters, + required this.sequence, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['asset_id'] = Variable(assetId); + map['action'] = Variable(action); + map['parameters'] = Variable(parameters); + map['sequence'] = Variable(sequence); + return map; + } + + factory AssetEditEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AssetEditEntityData( + id: serializer.fromJson(json['id']), + assetId: serializer.fromJson(json['assetId']), + action: serializer.fromJson(json['action']), + parameters: serializer.fromJson(json['parameters']), + sequence: serializer.fromJson(json['sequence']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'assetId': serializer.toJson(assetId), + 'action': serializer.toJson(action), + 'parameters': serializer.toJson(parameters), + 'sequence': serializer.toJson(sequence), + }; + } + + AssetEditEntityData copyWith({ + String? id, + String? assetId, + int? action, + i2.Uint8List? parameters, + int? sequence, + }) => AssetEditEntityData( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + action: action ?? this.action, + parameters: parameters ?? this.parameters, + sequence: sequence ?? this.sequence, + ); + AssetEditEntityData copyWithCompanion(AssetEditEntityCompanion data) { + return AssetEditEntityData( + id: data.id.present ? data.id.value : this.id, + assetId: data.assetId.present ? data.assetId.value : this.assetId, + action: data.action.present ? data.action.value : this.action, + parameters: data.parameters.present + ? data.parameters.value + : this.parameters, + sequence: data.sequence.present ? data.sequence.value : this.sequence, + ); + } + + @override + String toString() { + return (StringBuffer('AssetEditEntityData(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('action: $action, ') + ..write('parameters: $parameters, ') + ..write('sequence: $sequence') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + assetId, + action, + $driftBlobEquality.hash(parameters), + sequence, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AssetEditEntityData && + other.id == this.id && + other.assetId == this.assetId && + other.action == this.action && + $driftBlobEquality.equals(other.parameters, this.parameters) && + other.sequence == this.sequence); +} + +class AssetEditEntityCompanion extends UpdateCompanion { + final Value id; + final Value assetId; + final Value action; + final Value parameters; + final Value sequence; + const AssetEditEntityCompanion({ + this.id = const Value.absent(), + this.assetId = const Value.absent(), + this.action = const Value.absent(), + this.parameters = const Value.absent(), + this.sequence = const Value.absent(), + }); + AssetEditEntityCompanion.insert({ + required String id, + required String assetId, + required int action, + required i2.Uint8List parameters, + required int sequence, + }) : id = Value(id), + assetId = Value(assetId), + action = Value(action), + parameters = Value(parameters), + sequence = Value(sequence); + static Insertable custom({ + Expression? id, + Expression? assetId, + Expression? action, + Expression? parameters, + Expression? sequence, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (assetId != null) 'asset_id': assetId, + if (action != null) 'action': action, + if (parameters != null) 'parameters': parameters, + if (sequence != null) 'sequence': sequence, + }); + } + + AssetEditEntityCompanion copyWith({ + Value? id, + Value? assetId, + Value? action, + Value? parameters, + Value? sequence, + }) { + return AssetEditEntityCompanion( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + action: action ?? this.action, + parameters: parameters ?? this.parameters, + sequence: sequence ?? this.sequence, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (action.present) { + map['action'] = Variable(action.value); + } + if (parameters.present) { + map['parameters'] = Variable(parameters.value); + } + if (sequence.present) { + map['sequence'] = Variable(sequence.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AssetEditEntityCompanion(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('action: $action, ') + ..write('parameters: $parameters, ') + ..write('sequence: $sequence') + ..write(')')) + .toString(); + } +} + +class Settings extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + Settings(this.attachedDatabase, [this._alias]); + late final GeneratedColumn key = GeneratedColumn( + 'key', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn value = GeneratedColumn( + 'value', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + @override + List get $columns => [key, value, updatedAt]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'settings'; + @override + Set get $primaryKey => {key}; + @override + SettingsData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return SettingsData( + key: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}key'], + )!, + value: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}value'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + ); + } + + @override + Settings createAlias(String alias) { + return Settings(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY("key")']; + @override + bool get dontWriteConstraints => true; +} + +class SettingsData extends DataClass implements Insertable { + final String key; + final String value; + final String updatedAt; + const SettingsData({ + required this.key, + required this.value, + required this.updatedAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['key'] = Variable(key); + map['value'] = Variable(value); + map['updated_at'] = Variable(updatedAt); + return map; + } + + factory SettingsData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return SettingsData( + key: serializer.fromJson(json['key']), + value: serializer.fromJson(json['value']), + updatedAt: serializer.fromJson(json['updatedAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'key': serializer.toJson(key), + 'value': serializer.toJson(value), + 'updatedAt': serializer.toJson(updatedAt), + }; + } + + SettingsData copyWith({String? key, String? value, String? updatedAt}) => + SettingsData( + key: key ?? this.key, + value: value ?? this.value, + updatedAt: updatedAt ?? this.updatedAt, + ); + SettingsData copyWithCompanion(SettingsCompanion data) { + return SettingsData( + key: data.key.present ? data.key.value : this.key, + value: data.value.present ? data.value.value : this.value, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ); + } + + @override + String toString() { + return (StringBuffer('SettingsData(') + ..write('key: $key, ') + ..write('value: $value, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(key, value, updatedAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is SettingsData && + other.key == this.key && + other.value == this.value && + other.updatedAt == this.updatedAt); +} + +class SettingsCompanion extends UpdateCompanion { + final Value key; + final Value value; + final Value updatedAt; + const SettingsCompanion({ + this.key = const Value.absent(), + this.value = const Value.absent(), + this.updatedAt = const Value.absent(), + }); + SettingsCompanion.insert({ + required String key, + required String value, + this.updatedAt = const Value.absent(), + }) : key = Value(key), + value = Value(value); + static Insertable custom({ + Expression? key, + Expression? value, + Expression? updatedAt, + }) { + return RawValuesInsertable({ + if (key != null) 'key': key, + if (value != null) 'value': value, + if (updatedAt != null) 'updated_at': updatedAt, + }); + } + + SettingsCompanion copyWith({ + Value? key, + Value? value, + Value? updatedAt, + }) { + return SettingsCompanion( + key: key ?? this.key, + value: value ?? this.value, + updatedAt: updatedAt ?? this.updatedAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (key.present) { + map['key'] = Variable(key.value); + } + if (value.present) { + map['value'] = Variable(value.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('SettingsCompanion(') + ..write('key: $key, ') + ..write('value: $value, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } +} + +class DatabaseAtV28 extends GeneratedDatabase { + DatabaseAtV28(QueryExecutor e) : super(e); + late final UserEntity userEntity = UserEntity(this); + late final RemoteAssetEntity remoteAssetEntity = RemoteAssetEntity(this); + late final StackEntity stackEntity = StackEntity(this); + late final LocalAssetEntity localAssetEntity = LocalAssetEntity(this); + late final RemoteAlbumEntity remoteAlbumEntity = RemoteAlbumEntity(this); + late final LocalAlbumEntity localAlbumEntity = LocalAlbumEntity(this); + late final LocalAlbumAssetEntity localAlbumAssetEntity = + LocalAlbumAssetEntity(this); + late final Index idxLocalAlbumAssetAlbumAsset = Index( + 'idx_local_album_asset_album_asset', + 'CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)', + ); + late final Index idxLocalAssetChecksum = Index( + 'idx_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)', + ); + late final Index idxLocalAssetCloudId = Index( + 'idx_local_asset_cloud_id', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)', + ); + late final Index idxLocalAssetCreatedAt = Index( + 'idx_local_asset_created_at', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_created_at ON local_asset_entity (created_at)', + ); + late final Index idxStackPrimaryAssetId = Index( + 'idx_stack_primary_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)', + ); + late final Index uQRemoteAssetsOwnerChecksum = Index( + 'UQ_remote_assets_owner_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)', + ); + late final Index uQRemoteAssetsOwnerLibraryChecksum = Index( + 'UQ_remote_assets_owner_library_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)', + ); + late final Index idxRemoteAssetChecksum = Index( + 'idx_remote_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)', + ); + late final Index idxRemoteAssetStackId = Index( + 'idx_remote_asset_stack_id', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)', + ); + late final Index idxRemoteAssetOwnerVisibilityDeletedCreated = Index( + 'idx_remote_asset_owner_visibility_deleted_created', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_visibility_deleted_created ON remote_asset_entity (owner_id, visibility, deleted_at, created_at DESC)', + ); + late final AuthUserEntity authUserEntity = AuthUserEntity(this); + late final UserMetadataEntity userMetadataEntity = UserMetadataEntity(this); + late final PartnerEntity partnerEntity = PartnerEntity(this); + late final RemoteExifEntity remoteExifEntity = RemoteExifEntity(this); + late final RemoteAlbumAssetEntity remoteAlbumAssetEntity = + RemoteAlbumAssetEntity(this); + late final RemoteAlbumUserEntity remoteAlbumUserEntity = + RemoteAlbumUserEntity(this); + late final RemoteAssetCloudIdEntity remoteAssetCloudIdEntity = + RemoteAssetCloudIdEntity(this); + late final MemoryEntity memoryEntity = MemoryEntity(this); + late final MemoryAssetEntity memoryAssetEntity = MemoryAssetEntity(this); + late final PersonEntity personEntity = PersonEntity(this); + late final AssetFaceEntity assetFaceEntity = AssetFaceEntity(this); + late final StoreEntity storeEntity = StoreEntity(this); + late final TrashedLocalAssetEntity trashedLocalAssetEntity = + TrashedLocalAssetEntity(this); + late final AssetEditEntity assetEditEntity = AssetEditEntity(this); + late final Settings settings = Settings(this); + late final Index idxPartnerSharedWithId = Index( + 'idx_partner_shared_with_id', + 'CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)', + ); + late final Index idxLatLng = Index( + 'idx_lat_lng', + 'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)', + ); + late final Index idxRemoteExifCity = Index( + 'idx_remote_exif_city', + 'CREATE INDEX IF NOT EXISTS idx_remote_exif_city ON remote_exif_entity (city) WHERE city IS NOT NULL', + ); + late final Index idxRemoteAlbumAssetAlbumAsset = Index( + 'idx_remote_album_asset_album_asset', + 'CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)', + ); + late final Index idxRemoteAssetCloudId = Index( + 'idx_remote_asset_cloud_id', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)', + ); + late final Index idxPersonOwnerId = Index( + 'idx_person_owner_id', + 'CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)', + ); + late final Index idxAssetFacePersonId = Index( + 'idx_asset_face_person_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)', + ); + late final Index idxAssetFaceAssetId = Index( + 'idx_asset_face_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)', + ); + late final Index idxAssetFaceVisiblePerson = Index( + 'idx_asset_face_visible_person', + 'CREATE INDEX IF NOT EXISTS idx_asset_face_visible_person ON asset_face_entity (person_id, asset_id) WHERE is_visible = 1 AND deleted_at IS NULL', + ); + late final Index idxTrashedLocalAssetChecksum = Index( + 'idx_trashed_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)', + ); + late final Index idxTrashedLocalAssetAlbum = Index( + 'idx_trashed_local_asset_album', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)', + ); + late final Index idxAssetEditAssetId = Index( + 'idx_asset_edit_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_edit_asset_id ON asset_edit_entity (asset_id)', + ); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [ + userEntity, + remoteAssetEntity, + stackEntity, + localAssetEntity, + remoteAlbumEntity, + localAlbumEntity, + localAlbumAssetEntity, + idxLocalAlbumAssetAlbumAsset, + idxLocalAssetChecksum, + idxLocalAssetCloudId, + idxLocalAssetCreatedAt, + idxStackPrimaryAssetId, + uQRemoteAssetsOwnerChecksum, + uQRemoteAssetsOwnerLibraryChecksum, + idxRemoteAssetChecksum, + idxRemoteAssetStackId, + idxRemoteAssetOwnerVisibilityDeletedCreated, + authUserEntity, + userMetadataEntity, + partnerEntity, + remoteExifEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + remoteAssetCloudIdEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + assetFaceEntity, + storeEntity, + trashedLocalAssetEntity, + assetEditEntity, + settings, + idxPartnerSharedWithId, + idxLatLng, + idxRemoteExifCity, + idxRemoteAlbumAssetAlbumAsset, + idxRemoteAssetCloudId, + idxPersonOwnerId, + idxAssetFacePersonId, + idxAssetFaceAssetId, + idxAssetFaceVisiblePerson, + idxTrashedLocalAssetChecksum, + idxTrashedLocalAssetAlbum, + idxAssetEditAssetId, + ]; + @override + StreamQueryUpdateRules get streamUpdateRules => const StreamQueryUpdateRules([ + WritePropagation( + on: TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('remote_asset_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('stack_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_asset_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('remote_album_entity', kind: UpdateKind.update)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_album_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('local_album_entity', kind: UpdateKind.update)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'local_asset_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [ + TableUpdate('local_album_asset_entity', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'local_album_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [ + TableUpdate('local_album_asset_entity', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('user_metadata_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('partner_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('partner_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_asset_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('remote_exif_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_asset_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [ + TableUpdate('remote_album_asset_entity', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_album_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [ + TableUpdate('remote_album_asset_entity', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_album_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [ + TableUpdate('remote_album_user_entity', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [ + TableUpdate('remote_album_user_entity', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_asset_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [ + TableUpdate('remote_asset_cloud_id_entity', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('memory_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_asset_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('memory_asset_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'memory_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('memory_asset_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('person_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_asset_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('asset_face_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'person_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('asset_face_entity', kind: UpdateKind.update)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_asset_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('asset_edit_entity', kind: UpdateKind.delete)], + ), + ]); + @override + int get schemaVersion => 28; + @override + DriftDatabaseOptions get options => + const DriftDatabaseOptions(storeDateTimeAsText: true); +} diff --git a/mobile/test/drift/main/generated/schema_v29.dart b/mobile/test/drift/main/generated/schema_v29.dart new file mode 100644 index 0000000000..ed721e3fe8 --- /dev/null +++ b/mobile/test/drift/main/generated/schema_v29.dart @@ -0,0 +1,10027 @@ +// dart format width=80 +import 'dart:typed_data' as i2; +// GENERATED BY drift_dev, DO NOT MODIFY. +// ignore_for_file: type=lint,unused_import +// +import 'package:drift/drift.dart'; + +class UserEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn email = GeneratedColumn( + 'email', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn hasProfileImage = GeneratedColumn( + 'has_profile_image', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: + 'NOT NULL DEFAULT 0 CHECK (has_profile_image IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn profileChangedAt = GeneratedColumn( + 'profile_changed_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn avatarColor = GeneratedColumn( + 'avatar_color', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0', + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + id, + name, + email, + hasProfileImage, + profileChangedAt, + avatarColor, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_entity'; + @override + Set get $primaryKey => {id}; + @override + UserEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + email: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}email'], + )!, + hasProfileImage: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}has_profile_image'], + )!, + profileChangedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}profile_changed_at'], + )!, + avatarColor: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}avatar_color'], + )!, + ); + } + + @override + UserEntity createAlias(String alias) { + return UserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class UserEntityData extends DataClass implements Insertable { + final String id; + final String name; + final String email; + final int hasProfileImage; + final String profileChangedAt; + final int avatarColor; + const UserEntityData({ + required this.id, + required this.name, + required this.email, + required this.hasProfileImage, + required this.profileChangedAt, + required this.avatarColor, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['email'] = Variable(email); + map['has_profile_image'] = Variable(hasProfileImage); + map['profile_changed_at'] = Variable(profileChangedAt); + map['avatar_color'] = Variable(avatarColor); + return map; + } + + factory UserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + email: serializer.fromJson(json['email']), + hasProfileImage: serializer.fromJson(json['hasProfileImage']), + profileChangedAt: serializer.fromJson(json['profileChangedAt']), + avatarColor: serializer.fromJson(json['avatarColor']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'email': serializer.toJson(email), + 'hasProfileImage': serializer.toJson(hasProfileImage), + 'profileChangedAt': serializer.toJson(profileChangedAt), + 'avatarColor': serializer.toJson(avatarColor), + }; + } + + UserEntityData copyWith({ + String? id, + String? name, + String? email, + int? hasProfileImage, + String? profileChangedAt, + int? avatarColor, + }) => UserEntityData( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + ); + UserEntityData copyWithCompanion(UserEntityCompanion data) { + return UserEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + email: data.email.present ? data.email.value : this.email, + hasProfileImage: data.hasProfileImage.present + ? data.hasProfileImage.value + : this.hasProfileImage, + profileChangedAt: data.profileChangedAt.present + ? data.profileChangedAt.value + : this.profileChangedAt, + avatarColor: data.avatarColor.present + ? data.avatarColor.value + : this.avatarColor, + ); + } + + @override + String toString() { + return (StringBuffer('UserEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + email, + hasProfileImage, + profileChangedAt, + avatarColor, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserEntityData && + other.id == this.id && + other.name == this.name && + other.email == this.email && + other.hasProfileImage == this.hasProfileImage && + other.profileChangedAt == this.profileChangedAt && + other.avatarColor == this.avatarColor); +} + +class UserEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value email; + final Value hasProfileImage; + final Value profileChangedAt; + final Value avatarColor; + const UserEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.email = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + }); + UserEntityCompanion.insert({ + required String id, + required String name, + required String email, + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + }) : id = Value(id), + name = Value(name), + email = Value(email); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? email, + Expression? hasProfileImage, + Expression? profileChangedAt, + Expression? avatarColor, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (email != null) 'email': email, + if (hasProfileImage != null) 'has_profile_image': hasProfileImage, + if (profileChangedAt != null) 'profile_changed_at': profileChangedAt, + if (avatarColor != null) 'avatar_color': avatarColor, + }); + } + + UserEntityCompanion copyWith({ + Value? id, + Value? name, + Value? email, + Value? hasProfileImage, + Value? profileChangedAt, + Value? avatarColor, + }) { + return UserEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (email.present) { + map['email'] = Variable(email.value); + } + if (hasProfileImage.present) { + map['has_profile_image'] = Variable(hasProfileImage.value); + } + if (profileChangedAt.present) { + map['profile_changed_at'] = Variable(profileChangedAt.value); + } + if (avatarColor.present) { + map['avatar_color'] = Variable(avatarColor.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor') + ..write(')')) + .toString(); + } +} + +class RemoteAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn durationMs = GeneratedColumn( + 'duration_ms', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0 CHECK (is_favorite IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL REFERENCES user_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn localDateTime = GeneratedColumn( + 'local_date_time', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn thumbHash = GeneratedColumn( + 'thumb_hash', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn uploadedAt = GeneratedColumn( + 'uploaded_at', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn livePhotoVideoId = GeneratedColumn( + 'live_photo_video_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn visibility = GeneratedColumn( + 'visibility', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn stackId = GeneratedColumn( + 'stack_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn libraryId = GeneratedColumn( + 'library_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn isEdited = GeneratedColumn( + 'is_edited', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0 CHECK (is_edited IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationMs, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + uploadedAt, + livePhotoVideoId, + visibility, + stackId, + libraryId, + isEdited, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_asset_entity'; + @override + Set get $primaryKey => {id}; + @override + RemoteAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationMs: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_ms'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + )!, + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_favorite'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + localDateTime: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}local_date_time'], + ), + thumbHash: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumb_hash'], + ), + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}deleted_at'], + ), + uploadedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}uploaded_at'], + ), + livePhotoVideoId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}live_photo_video_id'], + ), + visibility: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}visibility'], + )!, + stackId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}stack_id'], + ), + libraryId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}library_id'], + ), + isEdited: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_edited'], + )!, + ); + } + + @override + RemoteAssetEntity createAlias(String alias) { + return RemoteAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class RemoteAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final String createdAt; + final String updatedAt; + final int? width; + final int? height; + final int? durationMs; + final String id; + final String checksum; + final int isFavorite; + final String ownerId; + final String? localDateTime; + final String? thumbHash; + final String? deletedAt; + final String? uploadedAt; + final String? livePhotoVideoId; + final int visibility; + final String? stackId; + final String? libraryId; + final int isEdited; + const RemoteAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationMs, + required this.id, + required this.checksum, + required this.isFavorite, + required this.ownerId, + this.localDateTime, + this.thumbHash, + this.deletedAt, + this.uploadedAt, + this.livePhotoVideoId, + required this.visibility, + this.stackId, + this.libraryId, + required this.isEdited, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationMs != null) { + map['duration_ms'] = Variable(durationMs); + } + map['id'] = Variable(id); + map['checksum'] = Variable(checksum); + map['is_favorite'] = Variable(isFavorite); + map['owner_id'] = Variable(ownerId); + if (!nullToAbsent || localDateTime != null) { + map['local_date_time'] = Variable(localDateTime); + } + if (!nullToAbsent || thumbHash != null) { + map['thumb_hash'] = Variable(thumbHash); + } + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + if (!nullToAbsent || uploadedAt != null) { + map['uploaded_at'] = Variable(uploadedAt); + } + if (!nullToAbsent || livePhotoVideoId != null) { + map['live_photo_video_id'] = Variable(livePhotoVideoId); + } + map['visibility'] = Variable(visibility); + if (!nullToAbsent || stackId != null) { + map['stack_id'] = Variable(stackId); + } + if (!nullToAbsent || libraryId != null) { + map['library_id'] = Variable(libraryId); + } + map['is_edited'] = Variable(isEdited); + return map; + } + + factory RemoteAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationMs: serializer.fromJson(json['durationMs']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + ownerId: serializer.fromJson(json['ownerId']), + localDateTime: serializer.fromJson(json['localDateTime']), + thumbHash: serializer.fromJson(json['thumbHash']), + deletedAt: serializer.fromJson(json['deletedAt']), + uploadedAt: serializer.fromJson(json['uploadedAt']), + livePhotoVideoId: serializer.fromJson(json['livePhotoVideoId']), + visibility: serializer.fromJson(json['visibility']), + stackId: serializer.fromJson(json['stackId']), + libraryId: serializer.fromJson(json['libraryId']), + isEdited: serializer.fromJson(json['isEdited']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationMs': serializer.toJson(durationMs), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'ownerId': serializer.toJson(ownerId), + 'localDateTime': serializer.toJson(localDateTime), + 'thumbHash': serializer.toJson(thumbHash), + 'deletedAt': serializer.toJson(deletedAt), + 'uploadedAt': serializer.toJson(uploadedAt), + 'livePhotoVideoId': serializer.toJson(livePhotoVideoId), + 'visibility': serializer.toJson(visibility), + 'stackId': serializer.toJson(stackId), + 'libraryId': serializer.toJson(libraryId), + 'isEdited': serializer.toJson(isEdited), + }; + } + + RemoteAssetEntityData copyWith({ + String? name, + int? type, + String? createdAt, + String? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationMs = const Value.absent(), + String? id, + String? checksum, + int? isFavorite, + String? ownerId, + Value localDateTime = const Value.absent(), + Value thumbHash = const Value.absent(), + Value deletedAt = const Value.absent(), + Value uploadedAt = const Value.absent(), + Value livePhotoVideoId = const Value.absent(), + int? visibility, + Value stackId = const Value.absent(), + Value libraryId = const Value.absent(), + int? isEdited, + }) => RemoteAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationMs: durationMs.present ? durationMs.value : this.durationMs, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime.present + ? localDateTime.value + : this.localDateTime, + thumbHash: thumbHash.present ? thumbHash.value : this.thumbHash, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + uploadedAt: uploadedAt.present ? uploadedAt.value : this.uploadedAt, + livePhotoVideoId: livePhotoVideoId.present + ? livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId.present ? stackId.value : this.stackId, + libraryId: libraryId.present ? libraryId.value : this.libraryId, + isEdited: isEdited ?? this.isEdited, + ); + RemoteAssetEntityData copyWithCompanion(RemoteAssetEntityCompanion data) { + return RemoteAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationMs: data.durationMs.present + ? data.durationMs.value + : this.durationMs, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + localDateTime: data.localDateTime.present + ? data.localDateTime.value + : this.localDateTime, + thumbHash: data.thumbHash.present ? data.thumbHash.value : this.thumbHash, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + uploadedAt: data.uploadedAt.present + ? data.uploadedAt.value + : this.uploadedAt, + livePhotoVideoId: data.livePhotoVideoId.present + ? data.livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: data.visibility.present + ? data.visibility.value + : this.visibility, + stackId: data.stackId.present ? data.stackId.value : this.stackId, + libraryId: data.libraryId.present ? data.libraryId.value : this.libraryId, + isEdited: data.isEdited.present ? data.isEdited.value : this.isEdited, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationMs: $durationMs, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('uploadedAt: $uploadedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId, ') + ..write('libraryId: $libraryId, ') + ..write('isEdited: $isEdited') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationMs, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + uploadedAt, + livePhotoVideoId, + visibility, + stackId, + libraryId, + isEdited, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationMs == this.durationMs && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.ownerId == this.ownerId && + other.localDateTime == this.localDateTime && + other.thumbHash == this.thumbHash && + other.deletedAt == this.deletedAt && + other.uploadedAt == this.uploadedAt && + other.livePhotoVideoId == this.livePhotoVideoId && + other.visibility == this.visibility && + other.stackId == this.stackId && + other.libraryId == this.libraryId && + other.isEdited == this.isEdited); +} + +class RemoteAssetEntityCompanion + extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationMs; + final Value id; + final Value checksum; + final Value isFavorite; + final Value ownerId; + final Value localDateTime; + final Value thumbHash; + final Value deletedAt; + final Value uploadedAt; + final Value livePhotoVideoId; + final Value visibility; + final Value stackId; + final Value libraryId; + final Value isEdited; + const RemoteAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationMs = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.ownerId = const Value.absent(), + this.localDateTime = const Value.absent(), + this.thumbHash = const Value.absent(), + this.deletedAt = const Value.absent(), + this.uploadedAt = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + this.visibility = const Value.absent(), + this.stackId = const Value.absent(), + this.libraryId = const Value.absent(), + this.isEdited = const Value.absent(), + }); + RemoteAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationMs = const Value.absent(), + required String id, + required String checksum, + this.isFavorite = const Value.absent(), + required String ownerId, + this.localDateTime = const Value.absent(), + this.thumbHash = const Value.absent(), + this.deletedAt = const Value.absent(), + this.uploadedAt = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + required int visibility, + this.stackId = const Value.absent(), + this.libraryId = const Value.absent(), + this.isEdited = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id), + checksum = Value(checksum), + ownerId = Value(ownerId), + visibility = Value(visibility); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationMs, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? ownerId, + Expression? localDateTime, + Expression? thumbHash, + Expression? deletedAt, + Expression? uploadedAt, + Expression? livePhotoVideoId, + Expression? visibility, + Expression? stackId, + Expression? libraryId, + Expression? isEdited, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationMs != null) 'duration_ms': durationMs, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (ownerId != null) 'owner_id': ownerId, + if (localDateTime != null) 'local_date_time': localDateTime, + if (thumbHash != null) 'thumb_hash': thumbHash, + if (deletedAt != null) 'deleted_at': deletedAt, + if (uploadedAt != null) 'uploaded_at': uploadedAt, + if (livePhotoVideoId != null) 'live_photo_video_id': livePhotoVideoId, + if (visibility != null) 'visibility': visibility, + if (stackId != null) 'stack_id': stackId, + if (libraryId != null) 'library_id': libraryId, + if (isEdited != null) 'is_edited': isEdited, + }); + } + + RemoteAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationMs, + Value? id, + Value? checksum, + Value? isFavorite, + Value? ownerId, + Value? localDateTime, + Value? thumbHash, + Value? deletedAt, + Value? uploadedAt, + Value? livePhotoVideoId, + Value? visibility, + Value? stackId, + Value? libraryId, + Value? isEdited, + }) { + return RemoteAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationMs: durationMs ?? this.durationMs, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime ?? this.localDateTime, + thumbHash: thumbHash ?? this.thumbHash, + deletedAt: deletedAt ?? this.deletedAt, + uploadedAt: uploadedAt ?? this.uploadedAt, + livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId ?? this.stackId, + libraryId: libraryId ?? this.libraryId, + isEdited: isEdited ?? this.isEdited, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationMs.present) { + map['duration_ms'] = Variable(durationMs.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (localDateTime.present) { + map['local_date_time'] = Variable(localDateTime.value); + } + if (thumbHash.present) { + map['thumb_hash'] = Variable(thumbHash.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + if (uploadedAt.present) { + map['uploaded_at'] = Variable(uploadedAt.value); + } + if (livePhotoVideoId.present) { + map['live_photo_video_id'] = Variable(livePhotoVideoId.value); + } + if (visibility.present) { + map['visibility'] = Variable(visibility.value); + } + if (stackId.present) { + map['stack_id'] = Variable(stackId.value); + } + if (libraryId.present) { + map['library_id'] = Variable(libraryId.value); + } + if (isEdited.present) { + map['is_edited'] = Variable(isEdited.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationMs: $durationMs, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('uploadedAt: $uploadedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId, ') + ..write('libraryId: $libraryId, ') + ..write('isEdited: $isEdited') + ..write(')')) + .toString(); + } +} + +class StackEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + StackEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL REFERENCES user_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn primaryAssetId = GeneratedColumn( + 'primary_asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + primaryAssetId, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'stack_entity'; + @override + Set get $primaryKey => {id}; + @override + StackEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return StackEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + primaryAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}primary_asset_id'], + )!, + ); + } + + @override + StackEntity createAlias(String alias) { + return StackEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class StackEntityData extends DataClass implements Insertable { + final String id; + final String createdAt; + final String updatedAt; + final String ownerId; + final String primaryAssetId; + const StackEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.primaryAssetId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + map['primary_asset_id'] = Variable(primaryAssetId); + return map; + } + + factory StackEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return StackEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + primaryAssetId: serializer.fromJson(json['primaryAssetId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'primaryAssetId': serializer.toJson(primaryAssetId), + }; + } + + StackEntityData copyWith({ + String? id, + String? createdAt, + String? updatedAt, + String? ownerId, + String? primaryAssetId, + }) => StackEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + StackEntityData copyWithCompanion(StackEntityCompanion data) { + return StackEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + primaryAssetId: data.primaryAssetId.present + ? data.primaryAssetId.value + : this.primaryAssetId, + ); + } + + @override + String toString() { + return (StringBuffer('StackEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(id, createdAt, updatedAt, ownerId, primaryAssetId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is StackEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.primaryAssetId == this.primaryAssetId); +} + +class StackEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value primaryAssetId; + const StackEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.primaryAssetId = const Value.absent(), + }); + StackEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + required String primaryAssetId, + }) : id = Value(id), + ownerId = Value(ownerId), + primaryAssetId = Value(primaryAssetId); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? primaryAssetId, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (primaryAssetId != null) 'primary_asset_id': primaryAssetId, + }); + } + + StackEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? primaryAssetId, + }) { + return StackEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (primaryAssetId.present) { + map['primary_asset_id'] = Variable(primaryAssetId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('StackEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } +} + +class LocalAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn durationMs = GeneratedColumn( + 'duration_ms', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0 CHECK (is_favorite IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn iCloudId = GeneratedColumn( + 'i_cloud_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn adjustmentTime = GeneratedColumn( + 'adjustment_time', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn playbackStyle = GeneratedColumn( + 'playback_style', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0', + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationMs, + id, + checksum, + isFavorite, + orientation, + iCloudId, + adjustmentTime, + latitude, + longitude, + playbackStyle, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_asset_entity'; + @override + Set get $primaryKey => {id}; + @override + LocalAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationMs: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_ms'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_favorite'], + )!, + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}orientation'], + )!, + iCloudId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}i_cloud_id'], + ), + adjustmentTime: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}adjustment_time'], + ), + latitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + playbackStyle: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}playback_style'], + )!, + ); + } + + @override + LocalAssetEntity createAlias(String alias) { + return LocalAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class LocalAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final String createdAt; + final String updatedAt; + final int? width; + final int? height; + final int? durationMs; + final String id; + final String? checksum; + final int isFavorite; + final int orientation; + final String? iCloudId; + final String? adjustmentTime; + final double? latitude; + final double? longitude; + final int playbackStyle; + const LocalAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationMs, + required this.id, + this.checksum, + required this.isFavorite, + required this.orientation, + this.iCloudId, + this.adjustmentTime, + this.latitude, + this.longitude, + required this.playbackStyle, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationMs != null) { + map['duration_ms'] = Variable(durationMs); + } + map['id'] = Variable(id); + if (!nullToAbsent || checksum != null) { + map['checksum'] = Variable(checksum); + } + map['is_favorite'] = Variable(isFavorite); + map['orientation'] = Variable(orientation); + if (!nullToAbsent || iCloudId != null) { + map['i_cloud_id'] = Variable(iCloudId); + } + if (!nullToAbsent || adjustmentTime != null) { + map['adjustment_time'] = Variable(adjustmentTime); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = Variable(longitude); + } + map['playback_style'] = Variable(playbackStyle); + return map; + } + + factory LocalAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationMs: serializer.fromJson(json['durationMs']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + orientation: serializer.fromJson(json['orientation']), + iCloudId: serializer.fromJson(json['iCloudId']), + adjustmentTime: serializer.fromJson(json['adjustmentTime']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + playbackStyle: serializer.fromJson(json['playbackStyle']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationMs': serializer.toJson(durationMs), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'orientation': serializer.toJson(orientation), + 'iCloudId': serializer.toJson(iCloudId), + 'adjustmentTime': serializer.toJson(adjustmentTime), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + 'playbackStyle': serializer.toJson(playbackStyle), + }; + } + + LocalAssetEntityData copyWith({ + String? name, + int? type, + String? createdAt, + String? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationMs = const Value.absent(), + String? id, + Value checksum = const Value.absent(), + int? isFavorite, + int? orientation, + Value iCloudId = const Value.absent(), + Value adjustmentTime = const Value.absent(), + Value latitude = const Value.absent(), + Value longitude = const Value.absent(), + int? playbackStyle, + }) => LocalAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationMs: durationMs.present ? durationMs.value : this.durationMs, + id: id ?? this.id, + checksum: checksum.present ? checksum.value : this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + iCloudId: iCloudId.present ? iCloudId.value : this.iCloudId, + adjustmentTime: adjustmentTime.present + ? adjustmentTime.value + : this.adjustmentTime, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + playbackStyle: playbackStyle ?? this.playbackStyle, + ); + LocalAssetEntityData copyWithCompanion(LocalAssetEntityCompanion data) { + return LocalAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationMs: data.durationMs.present + ? data.durationMs.value + : this.durationMs, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + iCloudId: data.iCloudId.present ? data.iCloudId.value : this.iCloudId, + adjustmentTime: data.adjustmentTime.present + ? data.adjustmentTime.value + : this.adjustmentTime, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + playbackStyle: data.playbackStyle.present + ? data.playbackStyle.value + : this.playbackStyle, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationMs: $durationMs, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation, ') + ..write('iCloudId: $iCloudId, ') + ..write('adjustmentTime: $adjustmentTime, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('playbackStyle: $playbackStyle') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationMs, + id, + checksum, + isFavorite, + orientation, + iCloudId, + adjustmentTime, + latitude, + longitude, + playbackStyle, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationMs == this.durationMs && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.orientation == this.orientation && + other.iCloudId == this.iCloudId && + other.adjustmentTime == this.adjustmentTime && + other.latitude == this.latitude && + other.longitude == this.longitude && + other.playbackStyle == this.playbackStyle); +} + +class LocalAssetEntityCompanion extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationMs; + final Value id; + final Value checksum; + final Value isFavorite; + final Value orientation; + final Value iCloudId; + final Value adjustmentTime; + final Value latitude; + final Value longitude; + final Value playbackStyle; + const LocalAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationMs = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + this.iCloudId = const Value.absent(), + this.adjustmentTime = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.playbackStyle = const Value.absent(), + }); + LocalAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationMs = const Value.absent(), + required String id, + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + this.iCloudId = const Value.absent(), + this.adjustmentTime = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.playbackStyle = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationMs, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? orientation, + Expression? iCloudId, + Expression? adjustmentTime, + Expression? latitude, + Expression? longitude, + Expression? playbackStyle, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationMs != null) 'duration_ms': durationMs, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (orientation != null) 'orientation': orientation, + if (iCloudId != null) 'i_cloud_id': iCloudId, + if (adjustmentTime != null) 'adjustment_time': adjustmentTime, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + if (playbackStyle != null) 'playback_style': playbackStyle, + }); + } + + LocalAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationMs, + Value? id, + Value? checksum, + Value? isFavorite, + Value? orientation, + Value? iCloudId, + Value? adjustmentTime, + Value? latitude, + Value? longitude, + Value? playbackStyle, + }) { + return LocalAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationMs: durationMs ?? this.durationMs, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + iCloudId: iCloudId ?? this.iCloudId, + adjustmentTime: adjustmentTime ?? this.adjustmentTime, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + playbackStyle: playbackStyle ?? this.playbackStyle, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationMs.present) { + map['duration_ms'] = Variable(durationMs.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + if (iCloudId.present) { + map['i_cloud_id'] = Variable(iCloudId.value); + } + if (adjustmentTime.present) { + map['adjustment_time'] = Variable(adjustmentTime.value); + } + if (latitude.present) { + map['latitude'] = Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = Variable(longitude.value); + } + if (playbackStyle.present) { + map['playback_style'] = Variable(playbackStyle.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationMs: $durationMs, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation, ') + ..write('iCloudId: $iCloudId, ') + ..write('adjustmentTime: $adjustmentTime, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('playbackStyle: $playbackStyle') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT \'\'', + defaultValue: const CustomExpression('\'\''), + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn thumbnailAssetId = GeneratedColumn( + 'thumbnail_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: + 'NULL REFERENCES remote_asset_entity(id)ON DELETE SET NULL', + ); + late final GeneratedColumn isActivityEnabled = GeneratedColumn( + 'is_activity_enabled', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: + 'NOT NULL DEFAULT 1 CHECK (is_activity_enabled IN (0, 1))', + defaultValue: const CustomExpression('1'), + ); + late final GeneratedColumn order = GeneratedColumn( + 'order', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + @override + List get $columns => [ + id, + name, + description, + createdAt, + updatedAt, + thumbnailAssetId, + isActivityEnabled, + order, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_entity'; + @override + Set get $primaryKey => {id}; + @override + RemoteAlbumEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + thumbnailAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumbnail_asset_id'], + ), + isActivityEnabled: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_activity_enabled'], + )!, + order: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}order'], + )!, + ); + } + + @override + RemoteAlbumEntity createAlias(String alias) { + return RemoteAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class RemoteAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String description; + final String createdAt; + final String updatedAt; + final String? thumbnailAssetId; + final int isActivityEnabled; + final int order; + const RemoteAlbumEntityData({ + required this.id, + required this.name, + required this.description, + required this.createdAt, + required this.updatedAt, + this.thumbnailAssetId, + required this.isActivityEnabled, + required this.order, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['description'] = Variable(description); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || thumbnailAssetId != null) { + map['thumbnail_asset_id'] = Variable(thumbnailAssetId); + } + map['is_activity_enabled'] = Variable(isActivityEnabled); + map['order'] = Variable(order); + return map; + } + + factory RemoteAlbumEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + description: serializer.fromJson(json['description']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + thumbnailAssetId: serializer.fromJson(json['thumbnailAssetId']), + isActivityEnabled: serializer.fromJson(json['isActivityEnabled']), + order: serializer.fromJson(json['order']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'description': serializer.toJson(description), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'thumbnailAssetId': serializer.toJson(thumbnailAssetId), + 'isActivityEnabled': serializer.toJson(isActivityEnabled), + 'order': serializer.toJson(order), + }; + } + + RemoteAlbumEntityData copyWith({ + String? id, + String? name, + String? description, + String? createdAt, + String? updatedAt, + Value thumbnailAssetId = const Value.absent(), + int? isActivityEnabled, + int? order, + }) => RemoteAlbumEntityData( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + thumbnailAssetId: thumbnailAssetId.present + ? thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + RemoteAlbumEntityData copyWithCompanion(RemoteAlbumEntityCompanion data) { + return RemoteAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + description: data.description.present + ? data.description.value + : this.description, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + thumbnailAssetId: data.thumbnailAssetId.present + ? data.thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: data.isActivityEnabled.present + ? data.isActivityEnabled.value + : this.isActivityEnabled, + order: data.order.present ? data.order.value : this.order, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + description, + createdAt, + updatedAt, + thumbnailAssetId, + isActivityEnabled, + order, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.description == this.description && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.thumbnailAssetId == this.thumbnailAssetId && + other.isActivityEnabled == this.isActivityEnabled && + other.order == this.order); +} + +class RemoteAlbumEntityCompanion + extends UpdateCompanion { + final Value id; + final Value name; + final Value description; + final Value createdAt; + final Value updatedAt; + final Value thumbnailAssetId; + final Value isActivityEnabled; + final Value order; + const RemoteAlbumEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.description = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + this.order = const Value.absent(), + }); + RemoteAlbumEntityCompanion.insert({ + required String id, + required String name, + this.description = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + required int order, + }) : id = Value(id), + name = Value(name), + order = Value(order); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? description, + Expression? createdAt, + Expression? updatedAt, + Expression? thumbnailAssetId, + Expression? isActivityEnabled, + Expression? order, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (description != null) 'description': description, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (thumbnailAssetId != null) 'thumbnail_asset_id': thumbnailAssetId, + if (isActivityEnabled != null) 'is_activity_enabled': isActivityEnabled, + if (order != null) 'order': order, + }); + } + + RemoteAlbumEntityCompanion copyWith({ + Value? id, + Value? name, + Value? description, + Value? createdAt, + Value? updatedAt, + Value? thumbnailAssetId, + Value? isActivityEnabled, + Value? order, + }) { + return RemoteAlbumEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + thumbnailAssetId: thumbnailAssetId ?? this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (thumbnailAssetId.present) { + map['thumbnail_asset_id'] = Variable(thumbnailAssetId.value); + } + if (isActivityEnabled.present) { + map['is_activity_enabled'] = Variable(isActivityEnabled.value); + } + if (order.present) { + map['order'] = Variable(order.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } +} + +class LocalAlbumEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAlbumEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn backupSelection = GeneratedColumn( + 'backup_selection', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn isIosSharedAlbum = GeneratedColumn( + 'is_ios_shared_album', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: + 'NOT NULL DEFAULT 0 CHECK (is_ios_shared_album IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn linkedRemoteAlbumId = + GeneratedColumn( + 'linked_remote_album_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: + 'NULL REFERENCES remote_album_entity(id)ON DELETE SET NULL', + ); + late final GeneratedColumn marker = GeneratedColumn( + 'marker', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL CHECK (marker IN (0, 1))', + ); + @override + List get $columns => [ + id, + name, + updatedAt, + backupSelection, + isIosSharedAlbum, + linkedRemoteAlbumId, + marker, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_entity'; + @override + Set get $primaryKey => {id}; + @override + LocalAlbumEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAlbumEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + backupSelection: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}backup_selection'], + )!, + isIosSharedAlbum: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_ios_shared_album'], + )!, + linkedRemoteAlbumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}linked_remote_album_id'], + ), + marker: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}marker'], + ), + ); + } + + @override + LocalAlbumEntity createAlias(String alias) { + return LocalAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class LocalAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String updatedAt; + final int backupSelection; + final int isIosSharedAlbum; + final String? linkedRemoteAlbumId; + final int? marker; + const LocalAlbumEntityData({ + required this.id, + required this.name, + required this.updatedAt, + required this.backupSelection, + required this.isIosSharedAlbum, + this.linkedRemoteAlbumId, + this.marker, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['updated_at'] = Variable(updatedAt); + map['backup_selection'] = Variable(backupSelection); + map['is_ios_shared_album'] = Variable(isIosSharedAlbum); + if (!nullToAbsent || linkedRemoteAlbumId != null) { + map['linked_remote_album_id'] = Variable(linkedRemoteAlbumId); + } + if (!nullToAbsent || marker != null) { + map['marker'] = Variable(marker); + } + return map; + } + + factory LocalAlbumEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + updatedAt: serializer.fromJson(json['updatedAt']), + backupSelection: serializer.fromJson(json['backupSelection']), + isIosSharedAlbum: serializer.fromJson(json['isIosSharedAlbum']), + linkedRemoteAlbumId: serializer.fromJson( + json['linkedRemoteAlbumId'], + ), + marker: serializer.fromJson(json['marker']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'updatedAt': serializer.toJson(updatedAt), + 'backupSelection': serializer.toJson(backupSelection), + 'isIosSharedAlbum': serializer.toJson(isIosSharedAlbum), + 'linkedRemoteAlbumId': serializer.toJson(linkedRemoteAlbumId), + 'marker': serializer.toJson(marker), + }; + } + + LocalAlbumEntityData copyWith({ + String? id, + String? name, + String? updatedAt, + int? backupSelection, + int? isIosSharedAlbum, + Value linkedRemoteAlbumId = const Value.absent(), + Value marker = const Value.absent(), + }) => LocalAlbumEntityData( + id: id ?? this.id, + name: name ?? this.name, + updatedAt: updatedAt ?? this.updatedAt, + backupSelection: backupSelection ?? this.backupSelection, + isIosSharedAlbum: isIosSharedAlbum ?? this.isIosSharedAlbum, + linkedRemoteAlbumId: linkedRemoteAlbumId.present + ? linkedRemoteAlbumId.value + : this.linkedRemoteAlbumId, + marker: marker.present ? marker.value : this.marker, + ); + LocalAlbumEntityData copyWithCompanion(LocalAlbumEntityCompanion data) { + return LocalAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + backupSelection: data.backupSelection.present + ? data.backupSelection.value + : this.backupSelection, + isIosSharedAlbum: data.isIosSharedAlbum.present + ? data.isIosSharedAlbum.value + : this.isIosSharedAlbum, + linkedRemoteAlbumId: data.linkedRemoteAlbumId.present + ? data.linkedRemoteAlbumId.value + : this.linkedRemoteAlbumId, + marker: data.marker.present ? data.marker.value : this.marker, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('updatedAt: $updatedAt, ') + ..write('backupSelection: $backupSelection, ') + ..write('isIosSharedAlbum: $isIosSharedAlbum, ') + ..write('linkedRemoteAlbumId: $linkedRemoteAlbumId, ') + ..write('marker: $marker') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + updatedAt, + backupSelection, + isIosSharedAlbum, + linkedRemoteAlbumId, + marker, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.updatedAt == this.updatedAt && + other.backupSelection == this.backupSelection && + other.isIosSharedAlbum == this.isIosSharedAlbum && + other.linkedRemoteAlbumId == this.linkedRemoteAlbumId && + other.marker == this.marker); +} + +class LocalAlbumEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value updatedAt; + final Value backupSelection; + final Value isIosSharedAlbum; + final Value linkedRemoteAlbumId; + final Value marker; + const LocalAlbumEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.updatedAt = const Value.absent(), + this.backupSelection = const Value.absent(), + this.isIosSharedAlbum = const Value.absent(), + this.linkedRemoteAlbumId = const Value.absent(), + this.marker = const Value.absent(), + }); + LocalAlbumEntityCompanion.insert({ + required String id, + required String name, + this.updatedAt = const Value.absent(), + required int backupSelection, + this.isIosSharedAlbum = const Value.absent(), + this.linkedRemoteAlbumId = const Value.absent(), + this.marker = const Value.absent(), + }) : id = Value(id), + name = Value(name), + backupSelection = Value(backupSelection); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? updatedAt, + Expression? backupSelection, + Expression? isIosSharedAlbum, + Expression? linkedRemoteAlbumId, + Expression? marker, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (updatedAt != null) 'updated_at': updatedAt, + if (backupSelection != null) 'backup_selection': backupSelection, + if (isIosSharedAlbum != null) 'is_ios_shared_album': isIosSharedAlbum, + if (linkedRemoteAlbumId != null) + 'linked_remote_album_id': linkedRemoteAlbumId, + if (marker != null) 'marker': marker, + }); + } + + LocalAlbumEntityCompanion copyWith({ + Value? id, + Value? name, + Value? updatedAt, + Value? backupSelection, + Value? isIosSharedAlbum, + Value? linkedRemoteAlbumId, + Value? marker, + }) { + return LocalAlbumEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + updatedAt: updatedAt ?? this.updatedAt, + backupSelection: backupSelection ?? this.backupSelection, + isIosSharedAlbum: isIosSharedAlbum ?? this.isIosSharedAlbum, + linkedRemoteAlbumId: linkedRemoteAlbumId ?? this.linkedRemoteAlbumId, + marker: marker ?? this.marker, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (backupSelection.present) { + map['backup_selection'] = Variable(backupSelection.value); + } + if (isIosSharedAlbum.present) { + map['is_ios_shared_album'] = Variable(isIosSharedAlbum.value); + } + if (linkedRemoteAlbumId.present) { + map['linked_remote_album_id'] = Variable( + linkedRemoteAlbumId.value, + ); + } + if (marker.present) { + map['marker'] = Variable(marker.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('updatedAt: $updatedAt, ') + ..write('backupSelection: $backupSelection, ') + ..write('isIosSharedAlbum: $isIosSharedAlbum, ') + ..write('linkedRemoteAlbumId: $linkedRemoteAlbumId, ') + ..write('marker: $marker') + ..write(')')) + .toString(); + } +} + +class LocalAlbumAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAlbumAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES local_asset_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES local_album_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn marker = GeneratedColumn( + 'marker', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL CHECK (marker IN (0, 1))', + ); + @override + List get $columns => [assetId, albumId, marker]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_asset_entity'; + @override + Set get $primaryKey => {assetId, albumId}; + @override + LocalAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + marker: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}marker'], + ), + ); + } + + @override + LocalAlbumAssetEntity createAlias(String alias) { + return LocalAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const [ + 'PRIMARY KEY(asset_id, album_id)', + ]; + @override + bool get dontWriteConstraints => true; +} + +class LocalAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + final int? marker; + const LocalAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + this.marker, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['album_id'] = Variable(albumId); + if (!nullToAbsent || marker != null) { + map['marker'] = Variable(marker); + } + return map; + } + + factory LocalAlbumAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + marker: serializer.fromJson(json['marker']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + 'marker': serializer.toJson(marker), + }; + } + + LocalAlbumAssetEntityData copyWith({ + String? assetId, + String? albumId, + Value marker = const Value.absent(), + }) => LocalAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + marker: marker.present ? marker.value : this.marker, + ); + LocalAlbumAssetEntityData copyWithCompanion( + LocalAlbumAssetEntityCompanion data, + ) { + return LocalAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + marker: data.marker.present ? data.marker.value : this.marker, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId, ') + ..write('marker: $marker') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId, marker); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId && + other.marker == this.marker); +} + +class LocalAlbumAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value albumId; + final Value marker; + const LocalAlbumAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.albumId = const Value.absent(), + this.marker = const Value.absent(), + }); + LocalAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + this.marker = const Value.absent(), + }) : assetId = Value(assetId), + albumId = Value(albumId); + static Insertable custom({ + Expression? assetId, + Expression? albumId, + Expression? marker, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + if (marker != null) 'marker': marker, + }); + } + + LocalAlbumAssetEntityCompanion copyWith({ + Value? assetId, + Value? albumId, + Value? marker, + }) { + return LocalAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + marker: marker ?? this.marker, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (marker.present) { + map['marker'] = Variable(marker.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId, ') + ..write('marker: $marker') + ..write(')')) + .toString(); + } +} + +class AuthUserEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AuthUserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn email = GeneratedColumn( + 'email', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn isAdmin = GeneratedColumn( + 'is_admin', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0 CHECK (is_admin IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn hasProfileImage = GeneratedColumn( + 'has_profile_image', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: + 'NOT NULL DEFAULT 0 CHECK (has_profile_image IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn profileChangedAt = GeneratedColumn( + 'profile_changed_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn avatarColor = GeneratedColumn( + 'avatar_color', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn quotaSizeInBytes = GeneratedColumn( + 'quota_size_in_bytes', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn quotaUsageInBytes = GeneratedColumn( + 'quota_usage_in_bytes', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn pinCode = GeneratedColumn( + 'pin_code', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + @override + List get $columns => [ + id, + name, + email, + isAdmin, + hasProfileImage, + profileChangedAt, + avatarColor, + quotaSizeInBytes, + quotaUsageInBytes, + pinCode, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'auth_user_entity'; + @override + Set get $primaryKey => {id}; + @override + AuthUserEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AuthUserEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + email: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}email'], + )!, + isAdmin: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_admin'], + )!, + hasProfileImage: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}has_profile_image'], + )!, + profileChangedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}profile_changed_at'], + )!, + avatarColor: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}avatar_color'], + )!, + quotaSizeInBytes: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}quota_size_in_bytes'], + )!, + quotaUsageInBytes: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}quota_usage_in_bytes'], + )!, + pinCode: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}pin_code'], + ), + ); + } + + @override + AuthUserEntity createAlias(String alias) { + return AuthUserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class AuthUserEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String email; + final int isAdmin; + final int hasProfileImage; + final String profileChangedAt; + final int avatarColor; + final int quotaSizeInBytes; + final int quotaUsageInBytes; + final String? pinCode; + const AuthUserEntityData({ + required this.id, + required this.name, + required this.email, + required this.isAdmin, + required this.hasProfileImage, + required this.profileChangedAt, + required this.avatarColor, + required this.quotaSizeInBytes, + required this.quotaUsageInBytes, + this.pinCode, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['email'] = Variable(email); + map['is_admin'] = Variable(isAdmin); + map['has_profile_image'] = Variable(hasProfileImage); + map['profile_changed_at'] = Variable(profileChangedAt); + map['avatar_color'] = Variable(avatarColor); + map['quota_size_in_bytes'] = Variable(quotaSizeInBytes); + map['quota_usage_in_bytes'] = Variable(quotaUsageInBytes); + if (!nullToAbsent || pinCode != null) { + map['pin_code'] = Variable(pinCode); + } + return map; + } + + factory AuthUserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AuthUserEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + email: serializer.fromJson(json['email']), + isAdmin: serializer.fromJson(json['isAdmin']), + hasProfileImage: serializer.fromJson(json['hasProfileImage']), + profileChangedAt: serializer.fromJson(json['profileChangedAt']), + avatarColor: serializer.fromJson(json['avatarColor']), + quotaSizeInBytes: serializer.fromJson(json['quotaSizeInBytes']), + quotaUsageInBytes: serializer.fromJson(json['quotaUsageInBytes']), + pinCode: serializer.fromJson(json['pinCode']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'email': serializer.toJson(email), + 'isAdmin': serializer.toJson(isAdmin), + 'hasProfileImage': serializer.toJson(hasProfileImage), + 'profileChangedAt': serializer.toJson(profileChangedAt), + 'avatarColor': serializer.toJson(avatarColor), + 'quotaSizeInBytes': serializer.toJson(quotaSizeInBytes), + 'quotaUsageInBytes': serializer.toJson(quotaUsageInBytes), + 'pinCode': serializer.toJson(pinCode), + }; + } + + AuthUserEntityData copyWith({ + String? id, + String? name, + String? email, + int? isAdmin, + int? hasProfileImage, + String? profileChangedAt, + int? avatarColor, + int? quotaSizeInBytes, + int? quotaUsageInBytes, + Value pinCode = const Value.absent(), + }) => AuthUserEntityData( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + isAdmin: isAdmin ?? this.isAdmin, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + quotaSizeInBytes: quotaSizeInBytes ?? this.quotaSizeInBytes, + quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes, + pinCode: pinCode.present ? pinCode.value : this.pinCode, + ); + AuthUserEntityData copyWithCompanion(AuthUserEntityCompanion data) { + return AuthUserEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + email: data.email.present ? data.email.value : this.email, + isAdmin: data.isAdmin.present ? data.isAdmin.value : this.isAdmin, + hasProfileImage: data.hasProfileImage.present + ? data.hasProfileImage.value + : this.hasProfileImage, + profileChangedAt: data.profileChangedAt.present + ? data.profileChangedAt.value + : this.profileChangedAt, + avatarColor: data.avatarColor.present + ? data.avatarColor.value + : this.avatarColor, + quotaSizeInBytes: data.quotaSizeInBytes.present + ? data.quotaSizeInBytes.value + : this.quotaSizeInBytes, + quotaUsageInBytes: data.quotaUsageInBytes.present + ? data.quotaUsageInBytes.value + : this.quotaUsageInBytes, + pinCode: data.pinCode.present ? data.pinCode.value : this.pinCode, + ); + } + + @override + String toString() { + return (StringBuffer('AuthUserEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('isAdmin: $isAdmin, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor, ') + ..write('quotaSizeInBytes: $quotaSizeInBytes, ') + ..write('quotaUsageInBytes: $quotaUsageInBytes, ') + ..write('pinCode: $pinCode') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + email, + isAdmin, + hasProfileImage, + profileChangedAt, + avatarColor, + quotaSizeInBytes, + quotaUsageInBytes, + pinCode, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AuthUserEntityData && + other.id == this.id && + other.name == this.name && + other.email == this.email && + other.isAdmin == this.isAdmin && + other.hasProfileImage == this.hasProfileImage && + other.profileChangedAt == this.profileChangedAt && + other.avatarColor == this.avatarColor && + other.quotaSizeInBytes == this.quotaSizeInBytes && + other.quotaUsageInBytes == this.quotaUsageInBytes && + other.pinCode == this.pinCode); +} + +class AuthUserEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value email; + final Value isAdmin; + final Value hasProfileImage; + final Value profileChangedAt; + final Value avatarColor; + final Value quotaSizeInBytes; + final Value quotaUsageInBytes; + final Value pinCode; + const AuthUserEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.email = const Value.absent(), + this.isAdmin = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + this.quotaSizeInBytes = const Value.absent(), + this.quotaUsageInBytes = const Value.absent(), + this.pinCode = const Value.absent(), + }); + AuthUserEntityCompanion.insert({ + required String id, + required String name, + required String email, + this.isAdmin = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + required int avatarColor, + this.quotaSizeInBytes = const Value.absent(), + this.quotaUsageInBytes = const Value.absent(), + this.pinCode = const Value.absent(), + }) : id = Value(id), + name = Value(name), + email = Value(email), + avatarColor = Value(avatarColor); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? email, + Expression? isAdmin, + Expression? hasProfileImage, + Expression? profileChangedAt, + Expression? avatarColor, + Expression? quotaSizeInBytes, + Expression? quotaUsageInBytes, + Expression? pinCode, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (email != null) 'email': email, + if (isAdmin != null) 'is_admin': isAdmin, + if (hasProfileImage != null) 'has_profile_image': hasProfileImage, + if (profileChangedAt != null) 'profile_changed_at': profileChangedAt, + if (avatarColor != null) 'avatar_color': avatarColor, + if (quotaSizeInBytes != null) 'quota_size_in_bytes': quotaSizeInBytes, + if (quotaUsageInBytes != null) 'quota_usage_in_bytes': quotaUsageInBytes, + if (pinCode != null) 'pin_code': pinCode, + }); + } + + AuthUserEntityCompanion copyWith({ + Value? id, + Value? name, + Value? email, + Value? isAdmin, + Value? hasProfileImage, + Value? profileChangedAt, + Value? avatarColor, + Value? quotaSizeInBytes, + Value? quotaUsageInBytes, + Value? pinCode, + }) { + return AuthUserEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + isAdmin: isAdmin ?? this.isAdmin, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + quotaSizeInBytes: quotaSizeInBytes ?? this.quotaSizeInBytes, + quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes, + pinCode: pinCode ?? this.pinCode, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (email.present) { + map['email'] = Variable(email.value); + } + if (isAdmin.present) { + map['is_admin'] = Variable(isAdmin.value); + } + if (hasProfileImage.present) { + map['has_profile_image'] = Variable(hasProfileImage.value); + } + if (profileChangedAt.present) { + map['profile_changed_at'] = Variable(profileChangedAt.value); + } + if (avatarColor.present) { + map['avatar_color'] = Variable(avatarColor.value); + } + if (quotaSizeInBytes.present) { + map['quota_size_in_bytes'] = Variable(quotaSizeInBytes.value); + } + if (quotaUsageInBytes.present) { + map['quota_usage_in_bytes'] = Variable(quotaUsageInBytes.value); + } + if (pinCode.present) { + map['pin_code'] = Variable(pinCode.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AuthUserEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('isAdmin: $isAdmin, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor, ') + ..write('quotaSizeInBytes: $quotaSizeInBytes, ') + ..write('quotaUsageInBytes: $quotaUsageInBytes, ') + ..write('pinCode: $pinCode') + ..write(')')) + .toString(); + } +} + +class UserMetadataEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UserMetadataEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL REFERENCES user_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn key = GeneratedColumn( + 'key', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn value = + GeneratedColumn( + 'value', + aliasedName, + false, + type: DriftSqlType.blob, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + @override + List get $columns => [userId, key, value]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_metadata_entity'; + @override + Set get $primaryKey => {userId, key}; + @override + UserMetadataEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserMetadataEntityData( + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + key: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}key'], + )!, + value: attachedDatabase.typeMapping.read( + DriftSqlType.blob, + data['${effectivePrefix}value'], + )!, + ); + } + + @override + UserMetadataEntity createAlias(String alias) { + return UserMetadataEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(user_id, "key")']; + @override + bool get dontWriteConstraints => true; +} + +class UserMetadataEntityData extends DataClass + implements Insertable { + final String userId; + final int key; + final i2.Uint8List value; + const UserMetadataEntityData({ + required this.userId, + required this.key, + required this.value, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['user_id'] = Variable(userId); + map['key'] = Variable(key); + map['value'] = Variable(value); + return map; + } + + factory UserMetadataEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserMetadataEntityData( + userId: serializer.fromJson(json['userId']), + key: serializer.fromJson(json['key']), + value: serializer.fromJson(json['value']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'userId': serializer.toJson(userId), + 'key': serializer.toJson(key), + 'value': serializer.toJson(value), + }; + } + + UserMetadataEntityData copyWith({ + String? userId, + int? key, + i2.Uint8List? value, + }) => UserMetadataEntityData( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + UserMetadataEntityData copyWithCompanion(UserMetadataEntityCompanion data) { + return UserMetadataEntityData( + userId: data.userId.present ? data.userId.value : this.userId, + key: data.key.present ? data.key.value : this.key, + value: data.value.present ? data.value.value : this.value, + ); + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityData(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(userId, key, $driftBlobEquality.hash(value)); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserMetadataEntityData && + other.userId == this.userId && + other.key == this.key && + $driftBlobEquality.equals(other.value, this.value)); +} + +class UserMetadataEntityCompanion + extends UpdateCompanion { + final Value userId; + final Value key; + final Value value; + const UserMetadataEntityCompanion({ + this.userId = const Value.absent(), + this.key = const Value.absent(), + this.value = const Value.absent(), + }); + UserMetadataEntityCompanion.insert({ + required String userId, + required int key, + required i2.Uint8List value, + }) : userId = Value(userId), + key = Value(key), + value = Value(value); + static Insertable custom({ + Expression? userId, + Expression? key, + Expression? value, + }) { + return RawValuesInsertable({ + if (userId != null) 'user_id': userId, + if (key != null) 'key': key, + if (value != null) 'value': value, + }); + } + + UserMetadataEntityCompanion copyWith({ + Value? userId, + Value? key, + Value? value, + }) { + return UserMetadataEntityCompanion( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (key.present) { + map['key'] = Variable(key.value); + } + if (value.present) { + map['value'] = Variable(value.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityCompanion(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } +} + +class PartnerEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PartnerEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn sharedById = GeneratedColumn( + 'shared_by_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL REFERENCES user_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn sharedWithId = GeneratedColumn( + 'shared_with_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL REFERENCES user_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn inTimeline = GeneratedColumn( + 'in_timeline', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0 CHECK (in_timeline IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [sharedById, sharedWithId, inTimeline]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'partner_entity'; + @override + Set get $primaryKey => {sharedById, sharedWithId}; + @override + PartnerEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PartnerEntityData( + sharedById: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}shared_by_id'], + )!, + sharedWithId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}shared_with_id'], + )!, + inTimeline: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}in_timeline'], + )!, + ); + } + + @override + PartnerEntity createAlias(String alias) { + return PartnerEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const [ + 'PRIMARY KEY(shared_by_id, shared_with_id)', + ]; + @override + bool get dontWriteConstraints => true; +} + +class PartnerEntityData extends DataClass + implements Insertable { + final String sharedById; + final String sharedWithId; + final int inTimeline; + const PartnerEntityData({ + required this.sharedById, + required this.sharedWithId, + required this.inTimeline, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['shared_by_id'] = Variable(sharedById); + map['shared_with_id'] = Variable(sharedWithId); + map['in_timeline'] = Variable(inTimeline); + return map; + } + + factory PartnerEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PartnerEntityData( + sharedById: serializer.fromJson(json['sharedById']), + sharedWithId: serializer.fromJson(json['sharedWithId']), + inTimeline: serializer.fromJson(json['inTimeline']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'sharedById': serializer.toJson(sharedById), + 'sharedWithId': serializer.toJson(sharedWithId), + 'inTimeline': serializer.toJson(inTimeline), + }; + } + + PartnerEntityData copyWith({ + String? sharedById, + String? sharedWithId, + int? inTimeline, + }) => PartnerEntityData( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + PartnerEntityData copyWithCompanion(PartnerEntityCompanion data) { + return PartnerEntityData( + sharedById: data.sharedById.present + ? data.sharedById.value + : this.sharedById, + sharedWithId: data.sharedWithId.present + ? data.sharedWithId.value + : this.sharedWithId, + inTimeline: data.inTimeline.present + ? data.inTimeline.value + : this.inTimeline, + ); + } + + @override + String toString() { + return (StringBuffer('PartnerEntityData(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(sharedById, sharedWithId, inTimeline); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PartnerEntityData && + other.sharedById == this.sharedById && + other.sharedWithId == this.sharedWithId && + other.inTimeline == this.inTimeline); +} + +class PartnerEntityCompanion extends UpdateCompanion { + final Value sharedById; + final Value sharedWithId; + final Value inTimeline; + const PartnerEntityCompanion({ + this.sharedById = const Value.absent(), + this.sharedWithId = const Value.absent(), + this.inTimeline = const Value.absent(), + }); + PartnerEntityCompanion.insert({ + required String sharedById, + required String sharedWithId, + this.inTimeline = const Value.absent(), + }) : sharedById = Value(sharedById), + sharedWithId = Value(sharedWithId); + static Insertable custom({ + Expression? sharedById, + Expression? sharedWithId, + Expression? inTimeline, + }) { + return RawValuesInsertable({ + if (sharedById != null) 'shared_by_id': sharedById, + if (sharedWithId != null) 'shared_with_id': sharedWithId, + if (inTimeline != null) 'in_timeline': inTimeline, + }); + } + + PartnerEntityCompanion copyWith({ + Value? sharedById, + Value? sharedWithId, + Value? inTimeline, + }) { + return PartnerEntityCompanion( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (sharedById.present) { + map['shared_by_id'] = Variable(sharedById.value); + } + if (sharedWithId.present) { + map['shared_with_id'] = Variable(sharedWithId.value); + } + if (inTimeline.present) { + map['in_timeline'] = Variable(inTimeline.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PartnerEntityCompanion(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } +} + +class RemoteExifEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteExifEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES remote_asset_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn city = GeneratedColumn( + 'city', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn state = GeneratedColumn( + 'state', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn country = GeneratedColumn( + 'country', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn dateTimeOriginal = GeneratedColumn( + 'date_time_original', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn exposureTime = GeneratedColumn( + 'exposure_time', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn fNumber = GeneratedColumn( + 'f_number', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn fileSize = GeneratedColumn( + 'file_size', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn focalLength = GeneratedColumn( + 'focal_length', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn iso = GeneratedColumn( + 'iso', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn make = GeneratedColumn( + 'make', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn model = GeneratedColumn( + 'model', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn lens = GeneratedColumn( + 'lens', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn timeZone = GeneratedColumn( + 'time_zone', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn rating = GeneratedColumn( + 'rating', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn projectionType = GeneratedColumn( + 'projection_type', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + @override + List get $columns => [ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_exif_entity'; + @override + Set get $primaryKey => {assetId}; + @override + RemoteExifEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteExifEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + city: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}city'], + ), + state: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}state'], + ), + country: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}country'], + ), + dateTimeOriginal: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}date_time_original'], + ), + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + exposureTime: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}exposure_time'], + ), + fNumber: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}f_number'], + ), + fileSize: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}file_size'], + ), + focalLength: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}focal_length'], + ), + latitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + iso: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}iso'], + ), + make: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}make'], + ), + model: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}model'], + ), + lens: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}lens'], + ), + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}orientation'], + ), + timeZone: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}time_zone'], + ), + rating: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}rating'], + ), + projectionType: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}projection_type'], + ), + ); + } + + @override + RemoteExifEntity createAlias(String alias) { + return RemoteExifEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(asset_id)']; + @override + bool get dontWriteConstraints => true; +} + +class RemoteExifEntityData extends DataClass + implements Insertable { + final String assetId; + final String? city; + final String? state; + final String? country; + final String? dateTimeOriginal; + final String? description; + final int? height; + final int? width; + final String? exposureTime; + final double? fNumber; + final int? fileSize; + final double? focalLength; + final double? latitude; + final double? longitude; + final int? iso; + final String? make; + final String? model; + final String? lens; + final String? orientation; + final String? timeZone; + final int? rating; + final String? projectionType; + const RemoteExifEntityData({ + required this.assetId, + this.city, + this.state, + this.country, + this.dateTimeOriginal, + this.description, + this.height, + this.width, + this.exposureTime, + this.fNumber, + this.fileSize, + this.focalLength, + this.latitude, + this.longitude, + this.iso, + this.make, + this.model, + this.lens, + this.orientation, + this.timeZone, + this.rating, + this.projectionType, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || city != null) { + map['city'] = Variable(city); + } + if (!nullToAbsent || state != null) { + map['state'] = Variable(state); + } + if (!nullToAbsent || country != null) { + map['country'] = Variable(country); + } + if (!nullToAbsent || dateTimeOriginal != null) { + map['date_time_original'] = Variable(dateTimeOriginal); + } + if (!nullToAbsent || description != null) { + map['description'] = Variable(description); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || exposureTime != null) { + map['exposure_time'] = Variable(exposureTime); + } + if (!nullToAbsent || fNumber != null) { + map['f_number'] = Variable(fNumber); + } + if (!nullToAbsent || fileSize != null) { + map['file_size'] = Variable(fileSize); + } + if (!nullToAbsent || focalLength != null) { + map['focal_length'] = Variable(focalLength); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = Variable(longitude); + } + if (!nullToAbsent || iso != null) { + map['iso'] = Variable(iso); + } + if (!nullToAbsent || make != null) { + map['make'] = Variable(make); + } + if (!nullToAbsent || model != null) { + map['model'] = Variable(model); + } + if (!nullToAbsent || lens != null) { + map['lens'] = Variable(lens); + } + if (!nullToAbsent || orientation != null) { + map['orientation'] = Variable(orientation); + } + if (!nullToAbsent || timeZone != null) { + map['time_zone'] = Variable(timeZone); + } + if (!nullToAbsent || rating != null) { + map['rating'] = Variable(rating); + } + if (!nullToAbsent || projectionType != null) { + map['projection_type'] = Variable(projectionType); + } + return map; + } + + factory RemoteExifEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteExifEntityData( + assetId: serializer.fromJson(json['assetId']), + city: serializer.fromJson(json['city']), + state: serializer.fromJson(json['state']), + country: serializer.fromJson(json['country']), + dateTimeOriginal: serializer.fromJson(json['dateTimeOriginal']), + description: serializer.fromJson(json['description']), + height: serializer.fromJson(json['height']), + width: serializer.fromJson(json['width']), + exposureTime: serializer.fromJson(json['exposureTime']), + fNumber: serializer.fromJson(json['fNumber']), + fileSize: serializer.fromJson(json['fileSize']), + focalLength: serializer.fromJson(json['focalLength']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + iso: serializer.fromJson(json['iso']), + make: serializer.fromJson(json['make']), + model: serializer.fromJson(json['model']), + lens: serializer.fromJson(json['lens']), + orientation: serializer.fromJson(json['orientation']), + timeZone: serializer.fromJson(json['timeZone']), + rating: serializer.fromJson(json['rating']), + projectionType: serializer.fromJson(json['projectionType']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'city': serializer.toJson(city), + 'state': serializer.toJson(state), + 'country': serializer.toJson(country), + 'dateTimeOriginal': serializer.toJson(dateTimeOriginal), + 'description': serializer.toJson(description), + 'height': serializer.toJson(height), + 'width': serializer.toJson(width), + 'exposureTime': serializer.toJson(exposureTime), + 'fNumber': serializer.toJson(fNumber), + 'fileSize': serializer.toJson(fileSize), + 'focalLength': serializer.toJson(focalLength), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + 'iso': serializer.toJson(iso), + 'make': serializer.toJson(make), + 'model': serializer.toJson(model), + 'lens': serializer.toJson(lens), + 'orientation': serializer.toJson(orientation), + 'timeZone': serializer.toJson(timeZone), + 'rating': serializer.toJson(rating), + 'projectionType': serializer.toJson(projectionType), + }; + } + + RemoteExifEntityData copyWith({ + String? assetId, + Value city = const Value.absent(), + Value state = const Value.absent(), + Value country = const Value.absent(), + Value dateTimeOriginal = const Value.absent(), + Value description = const Value.absent(), + Value height = const Value.absent(), + Value width = const Value.absent(), + Value exposureTime = const Value.absent(), + Value fNumber = const Value.absent(), + Value fileSize = const Value.absent(), + Value focalLength = const Value.absent(), + Value latitude = const Value.absent(), + Value longitude = const Value.absent(), + Value iso = const Value.absent(), + Value make = const Value.absent(), + Value model = const Value.absent(), + Value lens = const Value.absent(), + Value orientation = const Value.absent(), + Value timeZone = const Value.absent(), + Value rating = const Value.absent(), + Value projectionType = const Value.absent(), + }) => RemoteExifEntityData( + assetId: assetId ?? this.assetId, + city: city.present ? city.value : this.city, + state: state.present ? state.value : this.state, + country: country.present ? country.value : this.country, + dateTimeOriginal: dateTimeOriginal.present + ? dateTimeOriginal.value + : this.dateTimeOriginal, + description: description.present ? description.value : this.description, + height: height.present ? height.value : this.height, + width: width.present ? width.value : this.width, + exposureTime: exposureTime.present ? exposureTime.value : this.exposureTime, + fNumber: fNumber.present ? fNumber.value : this.fNumber, + fileSize: fileSize.present ? fileSize.value : this.fileSize, + focalLength: focalLength.present ? focalLength.value : this.focalLength, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + iso: iso.present ? iso.value : this.iso, + make: make.present ? make.value : this.make, + model: model.present ? model.value : this.model, + lens: lens.present ? lens.value : this.lens, + orientation: orientation.present ? orientation.value : this.orientation, + timeZone: timeZone.present ? timeZone.value : this.timeZone, + rating: rating.present ? rating.value : this.rating, + projectionType: projectionType.present + ? projectionType.value + : this.projectionType, + ); + RemoteExifEntityData copyWithCompanion(RemoteExifEntityCompanion data) { + return RemoteExifEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + city: data.city.present ? data.city.value : this.city, + state: data.state.present ? data.state.value : this.state, + country: data.country.present ? data.country.value : this.country, + dateTimeOriginal: data.dateTimeOriginal.present + ? data.dateTimeOriginal.value + : this.dateTimeOriginal, + description: data.description.present + ? data.description.value + : this.description, + height: data.height.present ? data.height.value : this.height, + width: data.width.present ? data.width.value : this.width, + exposureTime: data.exposureTime.present + ? data.exposureTime.value + : this.exposureTime, + fNumber: data.fNumber.present ? data.fNumber.value : this.fNumber, + fileSize: data.fileSize.present ? data.fileSize.value : this.fileSize, + focalLength: data.focalLength.present + ? data.focalLength.value + : this.focalLength, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + iso: data.iso.present ? data.iso.value : this.iso, + make: data.make.present ? data.make.value : this.make, + model: data.model.present ? data.model.value : this.model, + lens: data.lens.present ? data.lens.value : this.lens, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + timeZone: data.timeZone.present ? data.timeZone.value : this.timeZone, + rating: data.rating.present ? data.rating.value : this.rating, + projectionType: data.projectionType.present + ? data.projectionType.value + : this.projectionType, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityData(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hashAll([ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteExifEntityData && + other.assetId == this.assetId && + other.city == this.city && + other.state == this.state && + other.country == this.country && + other.dateTimeOriginal == this.dateTimeOriginal && + other.description == this.description && + other.height == this.height && + other.width == this.width && + other.exposureTime == this.exposureTime && + other.fNumber == this.fNumber && + other.fileSize == this.fileSize && + other.focalLength == this.focalLength && + other.latitude == this.latitude && + other.longitude == this.longitude && + other.iso == this.iso && + other.make == this.make && + other.model == this.model && + other.lens == this.lens && + other.orientation == this.orientation && + other.timeZone == this.timeZone && + other.rating == this.rating && + other.projectionType == this.projectionType); +} + +class RemoteExifEntityCompanion extends UpdateCompanion { + final Value assetId; + final Value city; + final Value state; + final Value country; + final Value dateTimeOriginal; + final Value description; + final Value height; + final Value width; + final Value exposureTime; + final Value fNumber; + final Value fileSize; + final Value focalLength; + final Value latitude; + final Value longitude; + final Value iso; + final Value make; + final Value model; + final Value lens; + final Value orientation; + final Value timeZone; + final Value rating; + final Value projectionType; + const RemoteExifEntityCompanion({ + this.assetId = const Value.absent(), + this.city = const Value.absent(), + this.state = const Value.absent(), + this.country = const Value.absent(), + this.dateTimeOriginal = const Value.absent(), + this.description = const Value.absent(), + this.height = const Value.absent(), + this.width = const Value.absent(), + this.exposureTime = const Value.absent(), + this.fNumber = const Value.absent(), + this.fileSize = const Value.absent(), + this.focalLength = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.iso = const Value.absent(), + this.make = const Value.absent(), + this.model = const Value.absent(), + this.lens = const Value.absent(), + this.orientation = const Value.absent(), + this.timeZone = const Value.absent(), + this.rating = const Value.absent(), + this.projectionType = const Value.absent(), + }); + RemoteExifEntityCompanion.insert({ + required String assetId, + this.city = const Value.absent(), + this.state = const Value.absent(), + this.country = const Value.absent(), + this.dateTimeOriginal = const Value.absent(), + this.description = const Value.absent(), + this.height = const Value.absent(), + this.width = const Value.absent(), + this.exposureTime = const Value.absent(), + this.fNumber = const Value.absent(), + this.fileSize = const Value.absent(), + this.focalLength = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.iso = const Value.absent(), + this.make = const Value.absent(), + this.model = const Value.absent(), + this.lens = const Value.absent(), + this.orientation = const Value.absent(), + this.timeZone = const Value.absent(), + this.rating = const Value.absent(), + this.projectionType = const Value.absent(), + }) : assetId = Value(assetId); + static Insertable custom({ + Expression? assetId, + Expression? city, + Expression? state, + Expression? country, + Expression? dateTimeOriginal, + Expression? description, + Expression? height, + Expression? width, + Expression? exposureTime, + Expression? fNumber, + Expression? fileSize, + Expression? focalLength, + Expression? latitude, + Expression? longitude, + Expression? iso, + Expression? make, + Expression? model, + Expression? lens, + Expression? orientation, + Expression? timeZone, + Expression? rating, + Expression? projectionType, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (city != null) 'city': city, + if (state != null) 'state': state, + if (country != null) 'country': country, + if (dateTimeOriginal != null) 'date_time_original': dateTimeOriginal, + if (description != null) 'description': description, + if (height != null) 'height': height, + if (width != null) 'width': width, + if (exposureTime != null) 'exposure_time': exposureTime, + if (fNumber != null) 'f_number': fNumber, + if (fileSize != null) 'file_size': fileSize, + if (focalLength != null) 'focal_length': focalLength, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + if (iso != null) 'iso': iso, + if (make != null) 'make': make, + if (model != null) 'model': model, + if (lens != null) 'lens': lens, + if (orientation != null) 'orientation': orientation, + if (timeZone != null) 'time_zone': timeZone, + if (rating != null) 'rating': rating, + if (projectionType != null) 'projection_type': projectionType, + }); + } + + RemoteExifEntityCompanion copyWith({ + Value? assetId, + Value? city, + Value? state, + Value? country, + Value? dateTimeOriginal, + Value? description, + Value? height, + Value? width, + Value? exposureTime, + Value? fNumber, + Value? fileSize, + Value? focalLength, + Value? latitude, + Value? longitude, + Value? iso, + Value? make, + Value? model, + Value? lens, + Value? orientation, + Value? timeZone, + Value? rating, + Value? projectionType, + }) { + return RemoteExifEntityCompanion( + assetId: assetId ?? this.assetId, + city: city ?? this.city, + state: state ?? this.state, + country: country ?? this.country, + dateTimeOriginal: dateTimeOriginal ?? this.dateTimeOriginal, + description: description ?? this.description, + height: height ?? this.height, + width: width ?? this.width, + exposureTime: exposureTime ?? this.exposureTime, + fNumber: fNumber ?? this.fNumber, + fileSize: fileSize ?? this.fileSize, + focalLength: focalLength ?? this.focalLength, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + iso: iso ?? this.iso, + make: make ?? this.make, + model: model ?? this.model, + lens: lens ?? this.lens, + orientation: orientation ?? this.orientation, + timeZone: timeZone ?? this.timeZone, + rating: rating ?? this.rating, + projectionType: projectionType ?? this.projectionType, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (city.present) { + map['city'] = Variable(city.value); + } + if (state.present) { + map['state'] = Variable(state.value); + } + if (country.present) { + map['country'] = Variable(country.value); + } + if (dateTimeOriginal.present) { + map['date_time_original'] = Variable(dateTimeOriginal.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (exposureTime.present) { + map['exposure_time'] = Variable(exposureTime.value); + } + if (fNumber.present) { + map['f_number'] = Variable(fNumber.value); + } + if (fileSize.present) { + map['file_size'] = Variable(fileSize.value); + } + if (focalLength.present) { + map['focal_length'] = Variable(focalLength.value); + } + if (latitude.present) { + map['latitude'] = Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = Variable(longitude.value); + } + if (iso.present) { + map['iso'] = Variable(iso.value); + } + if (make.present) { + map['make'] = Variable(make.value); + } + if (model.present) { + map['model'] = Variable(model.value); + } + if (lens.present) { + map['lens'] = Variable(lens.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + if (timeZone.present) { + map['time_zone'] = Variable(timeZone.value); + } + if (rating.present) { + map['rating'] = Variable(rating.value); + } + if (projectionType.present) { + map['projection_type'] = Variable(projectionType.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES remote_asset_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES remote_album_entity(id)ON DELETE CASCADE', + ); + @override + List get $columns => [assetId, albumId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_asset_entity'; + @override + Set get $primaryKey => {assetId, albumId}; + @override + RemoteAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + ); + } + + @override + RemoteAlbumAssetEntity createAlias(String alias) { + return RemoteAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const [ + 'PRIMARY KEY(asset_id, album_id)', + ]; + @override + bool get dontWriteConstraints => true; +} + +class RemoteAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + const RemoteAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['album_id'] = Variable(albumId); + return map; + } + + factory RemoteAlbumAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + }; + } + + RemoteAlbumAssetEntityData copyWith({String? assetId, String? albumId}) => + RemoteAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + RemoteAlbumAssetEntityData copyWithCompanion( + RemoteAlbumAssetEntityCompanion data, + ) { + return RemoteAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId); +} + +class RemoteAlbumAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value albumId; + const RemoteAlbumAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.albumId = const Value.absent(), + }); + RemoteAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + }) : assetId = Value(assetId), + albumId = Value(albumId); + static Insertable custom({ + Expression? assetId, + Expression? albumId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + }); + } + + RemoteAlbumAssetEntityCompanion copyWith({ + Value? assetId, + Value? albumId, + }) { + return RemoteAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumUserEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumUserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES remote_album_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL REFERENCES user_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn role = GeneratedColumn( + 'role', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + @override + List get $columns => [albumId, userId, role]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_user_entity'; + @override + Set get $primaryKey => {albumId, userId}; + @override + RemoteAlbumUserEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumUserEntityData( + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + role: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}role'], + )!, + ); + } + + @override + RemoteAlbumUserEntity createAlias(String alias) { + return RemoteAlbumUserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const [ + 'PRIMARY KEY(album_id, user_id)', + ]; + @override + bool get dontWriteConstraints => true; +} + +class RemoteAlbumUserEntityData extends DataClass + implements Insertable { + final String albumId; + final String userId; + final int role; + const RemoteAlbumUserEntityData({ + required this.albumId, + required this.userId, + required this.role, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['album_id'] = Variable(albumId); + map['user_id'] = Variable(userId); + map['role'] = Variable(role); + return map; + } + + factory RemoteAlbumUserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumUserEntityData( + albumId: serializer.fromJson(json['albumId']), + userId: serializer.fromJson(json['userId']), + role: serializer.fromJson(json['role']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'albumId': serializer.toJson(albumId), + 'userId': serializer.toJson(userId), + 'role': serializer.toJson(role), + }; + } + + RemoteAlbumUserEntityData copyWith({ + String? albumId, + String? userId, + int? role, + }) => RemoteAlbumUserEntityData( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + RemoteAlbumUserEntityData copyWithCompanion( + RemoteAlbumUserEntityCompanion data, + ) { + return RemoteAlbumUserEntityData( + albumId: data.albumId.present ? data.albumId.value : this.albumId, + userId: data.userId.present ? data.userId.value : this.userId, + role: data.role.present ? data.role.value : this.role, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityData(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(albumId, userId, role); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumUserEntityData && + other.albumId == this.albumId && + other.userId == this.userId && + other.role == this.role); +} + +class RemoteAlbumUserEntityCompanion + extends UpdateCompanion { + final Value albumId; + final Value userId; + final Value role; + const RemoteAlbumUserEntityCompanion({ + this.albumId = const Value.absent(), + this.userId = const Value.absent(), + this.role = const Value.absent(), + }); + RemoteAlbumUserEntityCompanion.insert({ + required String albumId, + required String userId, + required int role, + }) : albumId = Value(albumId), + userId = Value(userId), + role = Value(role); + static Insertable custom({ + Expression? albumId, + Expression? userId, + Expression? role, + }) { + return RawValuesInsertable({ + if (albumId != null) 'album_id': albumId, + if (userId != null) 'user_id': userId, + if (role != null) 'role': role, + }); + } + + RemoteAlbumUserEntityCompanion copyWith({ + Value? albumId, + Value? userId, + Value? role, + }) { + return RemoteAlbumUserEntityCompanion( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (role.present) { + map['role'] = Variable(role.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityCompanion(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } +} + +class RemoteAssetCloudIdEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAssetCloudIdEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES remote_asset_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn cloudId = GeneratedColumn( + 'cloud_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn adjustmentTime = GeneratedColumn( + 'adjustment_time', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + @override + List get $columns => [ + assetId, + cloudId, + createdAt, + adjustmentTime, + latitude, + longitude, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_asset_cloud_id_entity'; + @override + Set get $primaryKey => {assetId}; + @override + RemoteAssetCloudIdEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAssetCloudIdEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + cloudId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}cloud_id'], + ), + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}created_at'], + ), + adjustmentTime: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}adjustment_time'], + ), + latitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + ); + } + + @override + RemoteAssetCloudIdEntity createAlias(String alias) { + return RemoteAssetCloudIdEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(asset_id)']; + @override + bool get dontWriteConstraints => true; +} + +class RemoteAssetCloudIdEntityData extends DataClass + implements Insertable { + final String assetId; + final String? cloudId; + final String? createdAt; + final String? adjustmentTime; + final double? latitude; + final double? longitude; + const RemoteAssetCloudIdEntityData({ + required this.assetId, + this.cloudId, + this.createdAt, + this.adjustmentTime, + this.latitude, + this.longitude, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || cloudId != null) { + map['cloud_id'] = Variable(cloudId); + } + if (!nullToAbsent || createdAt != null) { + map['created_at'] = Variable(createdAt); + } + if (!nullToAbsent || adjustmentTime != null) { + map['adjustment_time'] = Variable(adjustmentTime); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = Variable(longitude); + } + return map; + } + + factory RemoteAssetCloudIdEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAssetCloudIdEntityData( + assetId: serializer.fromJson(json['assetId']), + cloudId: serializer.fromJson(json['cloudId']), + createdAt: serializer.fromJson(json['createdAt']), + adjustmentTime: serializer.fromJson(json['adjustmentTime']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'cloudId': serializer.toJson(cloudId), + 'createdAt': serializer.toJson(createdAt), + 'adjustmentTime': serializer.toJson(adjustmentTime), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + }; + } + + RemoteAssetCloudIdEntityData copyWith({ + String? assetId, + Value cloudId = const Value.absent(), + Value createdAt = const Value.absent(), + Value adjustmentTime = const Value.absent(), + Value latitude = const Value.absent(), + Value longitude = const Value.absent(), + }) => RemoteAssetCloudIdEntityData( + assetId: assetId ?? this.assetId, + cloudId: cloudId.present ? cloudId.value : this.cloudId, + createdAt: createdAt.present ? createdAt.value : this.createdAt, + adjustmentTime: adjustmentTime.present + ? adjustmentTime.value + : this.adjustmentTime, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + ); + RemoteAssetCloudIdEntityData copyWithCompanion( + RemoteAssetCloudIdEntityCompanion data, + ) { + return RemoteAssetCloudIdEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + cloudId: data.cloudId.present ? data.cloudId.value : this.cloudId, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + adjustmentTime: data.adjustmentTime.present + ? data.adjustmentTime.value + : this.adjustmentTime, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAssetCloudIdEntityData(') + ..write('assetId: $assetId, ') + ..write('cloudId: $cloudId, ') + ..write('createdAt: $createdAt, ') + ..write('adjustmentTime: $adjustmentTime, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + assetId, + cloudId, + createdAt, + adjustmentTime, + latitude, + longitude, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAssetCloudIdEntityData && + other.assetId == this.assetId && + other.cloudId == this.cloudId && + other.createdAt == this.createdAt && + other.adjustmentTime == this.adjustmentTime && + other.latitude == this.latitude && + other.longitude == this.longitude); +} + +class RemoteAssetCloudIdEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value cloudId; + final Value createdAt; + final Value adjustmentTime; + final Value latitude; + final Value longitude; + const RemoteAssetCloudIdEntityCompanion({ + this.assetId = const Value.absent(), + this.cloudId = const Value.absent(), + this.createdAt = const Value.absent(), + this.adjustmentTime = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + }); + RemoteAssetCloudIdEntityCompanion.insert({ + required String assetId, + this.cloudId = const Value.absent(), + this.createdAt = const Value.absent(), + this.adjustmentTime = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + }) : assetId = Value(assetId); + static Insertable custom({ + Expression? assetId, + Expression? cloudId, + Expression? createdAt, + Expression? adjustmentTime, + Expression? latitude, + Expression? longitude, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (cloudId != null) 'cloud_id': cloudId, + if (createdAt != null) 'created_at': createdAt, + if (adjustmentTime != null) 'adjustment_time': adjustmentTime, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + }); + } + + RemoteAssetCloudIdEntityCompanion copyWith({ + Value? assetId, + Value? cloudId, + Value? createdAt, + Value? adjustmentTime, + Value? latitude, + Value? longitude, + }) { + return RemoteAssetCloudIdEntityCompanion( + assetId: assetId ?? this.assetId, + cloudId: cloudId ?? this.cloudId, + createdAt: createdAt ?? this.createdAt, + adjustmentTime: adjustmentTime ?? this.adjustmentTime, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (cloudId.present) { + map['cloud_id'] = Variable(cloudId.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (adjustmentTime.present) { + map['adjustment_time'] = Variable(adjustmentTime.value); + } + if (latitude.present) { + map['latitude'] = Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = Variable(longitude.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAssetCloudIdEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('cloudId: $cloudId, ') + ..write('createdAt: $createdAt, ') + ..write('adjustmentTime: $adjustmentTime, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude') + ..write(')')) + .toString(); + } +} + +class MemoryEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MemoryEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL REFERENCES user_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn data = GeneratedColumn( + 'data', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn isSaved = GeneratedColumn( + 'is_saved', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0 CHECK (is_saved IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn memoryAt = GeneratedColumn( + 'memory_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn seenAt = GeneratedColumn( + 'seen_at', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn showAt = GeneratedColumn( + 'show_at', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn hideAt = GeneratedColumn( + 'hide_at', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_entity'; + @override + Set get $primaryKey => {id}; + @override + MemoryEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MemoryEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}deleted_at'], + ), + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + data: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}data'], + )!, + isSaved: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_saved'], + )!, + memoryAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}memory_at'], + )!, + seenAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}seen_at'], + ), + showAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}show_at'], + ), + hideAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}hide_at'], + ), + ); + } + + @override + MemoryEntity createAlias(String alias) { + return MemoryEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class MemoryEntityData extends DataClass + implements Insertable { + final String id; + final String createdAt; + final String updatedAt; + final String? deletedAt; + final String ownerId; + final int type; + final String data; + final int isSaved; + final String memoryAt; + final String? seenAt; + final String? showAt; + final String? hideAt; + const MemoryEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + this.deletedAt, + required this.ownerId, + required this.type, + required this.data, + required this.isSaved, + required this.memoryAt, + this.seenAt, + this.showAt, + this.hideAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + map['owner_id'] = Variable(ownerId); + map['type'] = Variable(type); + map['data'] = Variable(data); + map['is_saved'] = Variable(isSaved); + map['memory_at'] = Variable(memoryAt); + if (!nullToAbsent || seenAt != null) { + map['seen_at'] = Variable(seenAt); + } + if (!nullToAbsent || showAt != null) { + map['show_at'] = Variable(showAt); + } + if (!nullToAbsent || hideAt != null) { + map['hide_at'] = Variable(hideAt); + } + return map; + } + + factory MemoryEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MemoryEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + deletedAt: serializer.fromJson(json['deletedAt']), + ownerId: serializer.fromJson(json['ownerId']), + type: serializer.fromJson(json['type']), + data: serializer.fromJson(json['data']), + isSaved: serializer.fromJson(json['isSaved']), + memoryAt: serializer.fromJson(json['memoryAt']), + seenAt: serializer.fromJson(json['seenAt']), + showAt: serializer.fromJson(json['showAt']), + hideAt: serializer.fromJson(json['hideAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'deletedAt': serializer.toJson(deletedAt), + 'ownerId': serializer.toJson(ownerId), + 'type': serializer.toJson(type), + 'data': serializer.toJson(data), + 'isSaved': serializer.toJson(isSaved), + 'memoryAt': serializer.toJson(memoryAt), + 'seenAt': serializer.toJson(seenAt), + 'showAt': serializer.toJson(showAt), + 'hideAt': serializer.toJson(hideAt), + }; + } + + MemoryEntityData copyWith({ + String? id, + String? createdAt, + String? updatedAt, + Value deletedAt = const Value.absent(), + String? ownerId, + int? type, + String? data, + int? isSaved, + String? memoryAt, + Value seenAt = const Value.absent(), + Value showAt = const Value.absent(), + Value hideAt = const Value.absent(), + }) => MemoryEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt.present ? seenAt.value : this.seenAt, + showAt: showAt.present ? showAt.value : this.showAt, + hideAt: hideAt.present ? hideAt.value : this.hideAt, + ); + MemoryEntityData copyWithCompanion(MemoryEntityCompanion data) { + return MemoryEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + type: data.type.present ? data.type.value : this.type, + data: data.data.present ? data.data.value : this.data, + isSaved: data.isSaved.present ? data.isSaved.value : this.isSaved, + memoryAt: data.memoryAt.present ? data.memoryAt.value : this.memoryAt, + seenAt: data.seenAt.present ? data.seenAt.value : this.seenAt, + showAt: data.showAt.present ? data.showAt.value : this.showAt, + hideAt: data.hideAt.present ? data.hideAt.value : this.hideAt, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MemoryEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.deletedAt == this.deletedAt && + other.ownerId == this.ownerId && + other.type == this.type && + other.data == this.data && + other.isSaved == this.isSaved && + other.memoryAt == this.memoryAt && + other.seenAt == this.seenAt && + other.showAt == this.showAt && + other.hideAt == this.hideAt); +} + +class MemoryEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value deletedAt; + final Value ownerId; + final Value type; + final Value data; + final Value isSaved; + final Value memoryAt; + final Value seenAt; + final Value showAt; + final Value hideAt; + const MemoryEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.deletedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.type = const Value.absent(), + this.data = const Value.absent(), + this.isSaved = const Value.absent(), + this.memoryAt = const Value.absent(), + this.seenAt = const Value.absent(), + this.showAt = const Value.absent(), + this.hideAt = const Value.absent(), + }); + MemoryEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.deletedAt = const Value.absent(), + required String ownerId, + required int type, + required String data, + this.isSaved = const Value.absent(), + required String memoryAt, + this.seenAt = const Value.absent(), + this.showAt = const Value.absent(), + this.hideAt = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + type = Value(type), + data = Value(data), + memoryAt = Value(memoryAt); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? deletedAt, + Expression? ownerId, + Expression? type, + Expression? data, + Expression? isSaved, + Expression? memoryAt, + Expression? seenAt, + Expression? showAt, + Expression? hideAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (deletedAt != null) 'deleted_at': deletedAt, + if (ownerId != null) 'owner_id': ownerId, + if (type != null) 'type': type, + if (data != null) 'data': data, + if (isSaved != null) 'is_saved': isSaved, + if (memoryAt != null) 'memory_at': memoryAt, + if (seenAt != null) 'seen_at': seenAt, + if (showAt != null) 'show_at': showAt, + if (hideAt != null) 'hide_at': hideAt, + }); + } + + MemoryEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? deletedAt, + Value? ownerId, + Value? type, + Value? data, + Value? isSaved, + Value? memoryAt, + Value? seenAt, + Value? showAt, + Value? hideAt, + }) { + return MemoryEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt ?? this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt ?? this.seenAt, + showAt: showAt ?? this.showAt, + hideAt: hideAt ?? this.hideAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (data.present) { + map['data'] = Variable(data.value); + } + if (isSaved.present) { + map['is_saved'] = Variable(isSaved.value); + } + if (memoryAt.present) { + map['memory_at'] = Variable(memoryAt.value); + } + if (seenAt.present) { + map['seen_at'] = Variable(seenAt.value); + } + if (showAt.present) { + map['show_at'] = Variable(showAt.value); + } + if (hideAt.present) { + map['hide_at'] = Variable(hideAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } +} + +class MemoryAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MemoryAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES remote_asset_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn memoryId = GeneratedColumn( + 'memory_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES memory_entity(id)ON DELETE CASCADE', + ); + @override + List get $columns => [assetId, memoryId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_asset_entity'; + @override + Set get $primaryKey => {assetId, memoryId}; + @override + MemoryAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MemoryAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + memoryId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}memory_id'], + )!, + ); + } + + @override + MemoryAssetEntity createAlias(String alias) { + return MemoryAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const [ + 'PRIMARY KEY(asset_id, memory_id)', + ]; + @override + bool get dontWriteConstraints => true; +} + +class MemoryAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String memoryId; + const MemoryAssetEntityData({required this.assetId, required this.memoryId}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['memory_id'] = Variable(memoryId); + return map; + } + + factory MemoryAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MemoryAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + memoryId: serializer.fromJson(json['memoryId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'memoryId': serializer.toJson(memoryId), + }; + } + + MemoryAssetEntityData copyWith({String? assetId, String? memoryId}) => + MemoryAssetEntityData( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + MemoryAssetEntityData copyWithCompanion(MemoryAssetEntityCompanion data) { + return MemoryAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + memoryId: data.memoryId.present ? data.memoryId.value : this.memoryId, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, memoryId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MemoryAssetEntityData && + other.assetId == this.assetId && + other.memoryId == this.memoryId); +} + +class MemoryAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value memoryId; + const MemoryAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.memoryId = const Value.absent(), + }); + MemoryAssetEntityCompanion.insert({ + required String assetId, + required String memoryId, + }) : assetId = Value(assetId), + memoryId = Value(memoryId); + static Insertable custom({ + Expression? assetId, + Expression? memoryId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (memoryId != null) 'memory_id': memoryId, + }); + } + + MemoryAssetEntityCompanion copyWith({ + Value? assetId, + Value? memoryId, + }) { + return MemoryAssetEntityCompanion( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (memoryId.present) { + map['memory_id'] = Variable(memoryId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } +} + +class PersonEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PersonEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL REFERENCES user_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn faceAssetId = GeneratedColumn( + 'face_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL CHECK (is_favorite IN (0, 1))', + ); + late final GeneratedColumn isHidden = GeneratedColumn( + 'is_hidden', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL CHECK (is_hidden IN (0, 1))', + ); + late final GeneratedColumn color = GeneratedColumn( + 'color', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn birthDate = GeneratedColumn( + 'birth_date', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + isFavorite, + isHidden, + color, + birthDate, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'person_entity'; + @override + Set get $primaryKey => {id}; + @override + PersonEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PersonEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + faceAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}face_asset_id'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_favorite'], + )!, + isHidden: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_hidden'], + )!, + color: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}color'], + ), + birthDate: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}birth_date'], + ), + ); + } + + @override + PersonEntity createAlias(String alias) { + return PersonEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class PersonEntityData extends DataClass + implements Insertable { + final String id; + final String createdAt; + final String updatedAt; + final String ownerId; + final String name; + final String? faceAssetId; + final int isFavorite; + final int isHidden; + final String? color; + final String? birthDate; + const PersonEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.name, + this.faceAssetId, + required this.isFavorite, + required this.isHidden, + this.color, + this.birthDate, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + map['name'] = Variable(name); + if (!nullToAbsent || faceAssetId != null) { + map['face_asset_id'] = Variable(faceAssetId); + } + map['is_favorite'] = Variable(isFavorite); + map['is_hidden'] = Variable(isHidden); + if (!nullToAbsent || color != null) { + map['color'] = Variable(color); + } + if (!nullToAbsent || birthDate != null) { + map['birth_date'] = Variable(birthDate); + } + return map; + } + + factory PersonEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PersonEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + name: serializer.fromJson(json['name']), + faceAssetId: serializer.fromJson(json['faceAssetId']), + isFavorite: serializer.fromJson(json['isFavorite']), + isHidden: serializer.fromJson(json['isHidden']), + color: serializer.fromJson(json['color']), + birthDate: serializer.fromJson(json['birthDate']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'name': serializer.toJson(name), + 'faceAssetId': serializer.toJson(faceAssetId), + 'isFavorite': serializer.toJson(isFavorite), + 'isHidden': serializer.toJson(isHidden), + 'color': serializer.toJson(color), + 'birthDate': serializer.toJson(birthDate), + }; + } + + PersonEntityData copyWith({ + String? id, + String? createdAt, + String? updatedAt, + String? ownerId, + String? name, + Value faceAssetId = const Value.absent(), + int? isFavorite, + int? isHidden, + Value color = const Value.absent(), + Value birthDate = const Value.absent(), + }) => PersonEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId.present ? faceAssetId.value : this.faceAssetId, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color.present ? color.value : this.color, + birthDate: birthDate.present ? birthDate.value : this.birthDate, + ); + PersonEntityData copyWithCompanion(PersonEntityCompanion data) { + return PersonEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + name: data.name.present ? data.name.value : this.name, + faceAssetId: data.faceAssetId.present + ? data.faceAssetId.value + : this.faceAssetId, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + isHidden: data.isHidden.present ? data.isHidden.value : this.isHidden, + color: data.color.present ? data.color.value : this.color, + birthDate: data.birthDate.present ? data.birthDate.value : this.birthDate, + ); + } + + @override + String toString() { + return (StringBuffer('PersonEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + isFavorite, + isHidden, + color, + birthDate, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PersonEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.name == this.name && + other.faceAssetId == this.faceAssetId && + other.isFavorite == this.isFavorite && + other.isHidden == this.isHidden && + other.color == this.color && + other.birthDate == this.birthDate); +} + +class PersonEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value name; + final Value faceAssetId; + final Value isFavorite; + final Value isHidden; + final Value color; + final Value birthDate; + const PersonEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.name = const Value.absent(), + this.faceAssetId = const Value.absent(), + this.isFavorite = const Value.absent(), + this.isHidden = const Value.absent(), + this.color = const Value.absent(), + this.birthDate = const Value.absent(), + }); + PersonEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + required String name, + this.faceAssetId = const Value.absent(), + required int isFavorite, + required int isHidden, + this.color = const Value.absent(), + this.birthDate = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + name = Value(name), + isFavorite = Value(isFavorite), + isHidden = Value(isHidden); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? name, + Expression? faceAssetId, + Expression? isFavorite, + Expression? isHidden, + Expression? color, + Expression? birthDate, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (name != null) 'name': name, + if (faceAssetId != null) 'face_asset_id': faceAssetId, + if (isFavorite != null) 'is_favorite': isFavorite, + if (isHidden != null) 'is_hidden': isHidden, + if (color != null) 'color': color, + if (birthDate != null) 'birth_date': birthDate, + }); + } + + PersonEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? name, + Value? faceAssetId, + Value? isFavorite, + Value? isHidden, + Value? color, + Value? birthDate, + }) { + return PersonEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId ?? this.faceAssetId, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color ?? this.color, + birthDate: birthDate ?? this.birthDate, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (faceAssetId.present) { + map['face_asset_id'] = Variable(faceAssetId.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (isHidden.present) { + map['is_hidden'] = Variable(isHidden.value); + } + if (color.present) { + map['color'] = Variable(color.value); + } + if (birthDate.present) { + map['birth_date'] = Variable(birthDate.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PersonEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } +} + +class AssetFaceEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AssetFaceEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES remote_asset_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn personId = GeneratedColumn( + 'person_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL REFERENCES person_entity(id)ON DELETE SET NULL', + ); + late final GeneratedColumn imageWidth = GeneratedColumn( + 'image_width', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn imageHeight = GeneratedColumn( + 'image_height', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn boundingBoxX1 = GeneratedColumn( + 'bounding_box_x1', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn boundingBoxY1 = GeneratedColumn( + 'bounding_box_y1', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn boundingBoxX2 = GeneratedColumn( + 'bounding_box_x2', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn boundingBoxY2 = GeneratedColumn( + 'bounding_box_y2', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn sourceType = GeneratedColumn( + 'source_type', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn isVisible = GeneratedColumn( + 'is_visible', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 1 CHECK (is_visible IN (0, 1))', + defaultValue: const CustomExpression('1'), + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + @override + List get $columns => [ + id, + assetId, + personId, + imageWidth, + imageHeight, + boundingBoxX1, + boundingBoxY1, + boundingBoxX2, + boundingBoxY2, + sourceType, + isVisible, + deletedAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'asset_face_entity'; + @override + Set get $primaryKey => {id}; + @override + AssetFaceEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AssetFaceEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + personId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}person_id'], + ), + imageWidth: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}image_width'], + )!, + imageHeight: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}image_height'], + )!, + boundingBoxX1: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_x1'], + )!, + boundingBoxY1: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_y1'], + )!, + boundingBoxX2: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_x2'], + )!, + boundingBoxY2: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_y2'], + )!, + sourceType: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}source_type'], + )!, + isVisible: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_visible'], + )!, + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}deleted_at'], + ), + ); + } + + @override + AssetFaceEntity createAlias(String alias) { + return AssetFaceEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class AssetFaceEntityData extends DataClass + implements Insertable { + final String id; + final String assetId; + final String? personId; + final int imageWidth; + final int imageHeight; + final int boundingBoxX1; + final int boundingBoxY1; + final int boundingBoxX2; + final int boundingBoxY2; + final String sourceType; + final int isVisible; + final String? deletedAt; + const AssetFaceEntityData({ + required this.id, + required this.assetId, + this.personId, + required this.imageWidth, + required this.imageHeight, + required this.boundingBoxX1, + required this.boundingBoxY1, + required this.boundingBoxX2, + required this.boundingBoxY2, + required this.sourceType, + required this.isVisible, + this.deletedAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || personId != null) { + map['person_id'] = Variable(personId); + } + map['image_width'] = Variable(imageWidth); + map['image_height'] = Variable(imageHeight); + map['bounding_box_x1'] = Variable(boundingBoxX1); + map['bounding_box_y1'] = Variable(boundingBoxY1); + map['bounding_box_x2'] = Variable(boundingBoxX2); + map['bounding_box_y2'] = Variable(boundingBoxY2); + map['source_type'] = Variable(sourceType); + map['is_visible'] = Variable(isVisible); + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + return map; + } + + factory AssetFaceEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AssetFaceEntityData( + id: serializer.fromJson(json['id']), + assetId: serializer.fromJson(json['assetId']), + personId: serializer.fromJson(json['personId']), + imageWidth: serializer.fromJson(json['imageWidth']), + imageHeight: serializer.fromJson(json['imageHeight']), + boundingBoxX1: serializer.fromJson(json['boundingBoxX1']), + boundingBoxY1: serializer.fromJson(json['boundingBoxY1']), + boundingBoxX2: serializer.fromJson(json['boundingBoxX2']), + boundingBoxY2: serializer.fromJson(json['boundingBoxY2']), + sourceType: serializer.fromJson(json['sourceType']), + isVisible: serializer.fromJson(json['isVisible']), + deletedAt: serializer.fromJson(json['deletedAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'assetId': serializer.toJson(assetId), + 'personId': serializer.toJson(personId), + 'imageWidth': serializer.toJson(imageWidth), + 'imageHeight': serializer.toJson(imageHeight), + 'boundingBoxX1': serializer.toJson(boundingBoxX1), + 'boundingBoxY1': serializer.toJson(boundingBoxY1), + 'boundingBoxX2': serializer.toJson(boundingBoxX2), + 'boundingBoxY2': serializer.toJson(boundingBoxY2), + 'sourceType': serializer.toJson(sourceType), + 'isVisible': serializer.toJson(isVisible), + 'deletedAt': serializer.toJson(deletedAt), + }; + } + + AssetFaceEntityData copyWith({ + String? id, + String? assetId, + Value personId = const Value.absent(), + int? imageWidth, + int? imageHeight, + int? boundingBoxX1, + int? boundingBoxY1, + int? boundingBoxX2, + int? boundingBoxY2, + String? sourceType, + int? isVisible, + Value deletedAt = const Value.absent(), + }) => AssetFaceEntityData( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + personId: personId.present ? personId.value : this.personId, + imageWidth: imageWidth ?? this.imageWidth, + imageHeight: imageHeight ?? this.imageHeight, + boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1, + boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1, + boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2, + boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2, + sourceType: sourceType ?? this.sourceType, + isVisible: isVisible ?? this.isVisible, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + ); + AssetFaceEntityData copyWithCompanion(AssetFaceEntityCompanion data) { + return AssetFaceEntityData( + id: data.id.present ? data.id.value : this.id, + assetId: data.assetId.present ? data.assetId.value : this.assetId, + personId: data.personId.present ? data.personId.value : this.personId, + imageWidth: data.imageWidth.present + ? data.imageWidth.value + : this.imageWidth, + imageHeight: data.imageHeight.present + ? data.imageHeight.value + : this.imageHeight, + boundingBoxX1: data.boundingBoxX1.present + ? data.boundingBoxX1.value + : this.boundingBoxX1, + boundingBoxY1: data.boundingBoxY1.present + ? data.boundingBoxY1.value + : this.boundingBoxY1, + boundingBoxX2: data.boundingBoxX2.present + ? data.boundingBoxX2.value + : this.boundingBoxX2, + boundingBoxY2: data.boundingBoxY2.present + ? data.boundingBoxY2.value + : this.boundingBoxY2, + sourceType: data.sourceType.present + ? data.sourceType.value + : this.sourceType, + isVisible: data.isVisible.present ? data.isVisible.value : this.isVisible, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + ); + } + + @override + String toString() { + return (StringBuffer('AssetFaceEntityData(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('personId: $personId, ') + ..write('imageWidth: $imageWidth, ') + ..write('imageHeight: $imageHeight, ') + ..write('boundingBoxX1: $boundingBoxX1, ') + ..write('boundingBoxY1: $boundingBoxY1, ') + ..write('boundingBoxX2: $boundingBoxX2, ') + ..write('boundingBoxY2: $boundingBoxY2, ') + ..write('sourceType: $sourceType, ') + ..write('isVisible: $isVisible, ') + ..write('deletedAt: $deletedAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + assetId, + personId, + imageWidth, + imageHeight, + boundingBoxX1, + boundingBoxY1, + boundingBoxX2, + boundingBoxY2, + sourceType, + isVisible, + deletedAt, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AssetFaceEntityData && + other.id == this.id && + other.assetId == this.assetId && + other.personId == this.personId && + other.imageWidth == this.imageWidth && + other.imageHeight == this.imageHeight && + other.boundingBoxX1 == this.boundingBoxX1 && + other.boundingBoxY1 == this.boundingBoxY1 && + other.boundingBoxX2 == this.boundingBoxX2 && + other.boundingBoxY2 == this.boundingBoxY2 && + other.sourceType == this.sourceType && + other.isVisible == this.isVisible && + other.deletedAt == this.deletedAt); +} + +class AssetFaceEntityCompanion extends UpdateCompanion { + final Value id; + final Value assetId; + final Value personId; + final Value imageWidth; + final Value imageHeight; + final Value boundingBoxX1; + final Value boundingBoxY1; + final Value boundingBoxX2; + final Value boundingBoxY2; + final Value sourceType; + final Value isVisible; + final Value deletedAt; + const AssetFaceEntityCompanion({ + this.id = const Value.absent(), + this.assetId = const Value.absent(), + this.personId = const Value.absent(), + this.imageWidth = const Value.absent(), + this.imageHeight = const Value.absent(), + this.boundingBoxX1 = const Value.absent(), + this.boundingBoxY1 = const Value.absent(), + this.boundingBoxX2 = const Value.absent(), + this.boundingBoxY2 = const Value.absent(), + this.sourceType = const Value.absent(), + this.isVisible = const Value.absent(), + this.deletedAt = const Value.absent(), + }); + AssetFaceEntityCompanion.insert({ + required String id, + required String assetId, + this.personId = const Value.absent(), + required int imageWidth, + required int imageHeight, + required int boundingBoxX1, + required int boundingBoxY1, + required int boundingBoxX2, + required int boundingBoxY2, + required String sourceType, + this.isVisible = const Value.absent(), + this.deletedAt = const Value.absent(), + }) : id = Value(id), + assetId = Value(assetId), + imageWidth = Value(imageWidth), + imageHeight = Value(imageHeight), + boundingBoxX1 = Value(boundingBoxX1), + boundingBoxY1 = Value(boundingBoxY1), + boundingBoxX2 = Value(boundingBoxX2), + boundingBoxY2 = Value(boundingBoxY2), + sourceType = Value(sourceType); + static Insertable custom({ + Expression? id, + Expression? assetId, + Expression? personId, + Expression? imageWidth, + Expression? imageHeight, + Expression? boundingBoxX1, + Expression? boundingBoxY1, + Expression? boundingBoxX2, + Expression? boundingBoxY2, + Expression? sourceType, + Expression? isVisible, + Expression? deletedAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (assetId != null) 'asset_id': assetId, + if (personId != null) 'person_id': personId, + if (imageWidth != null) 'image_width': imageWidth, + if (imageHeight != null) 'image_height': imageHeight, + if (boundingBoxX1 != null) 'bounding_box_x1': boundingBoxX1, + if (boundingBoxY1 != null) 'bounding_box_y1': boundingBoxY1, + if (boundingBoxX2 != null) 'bounding_box_x2': boundingBoxX2, + if (boundingBoxY2 != null) 'bounding_box_y2': boundingBoxY2, + if (sourceType != null) 'source_type': sourceType, + if (isVisible != null) 'is_visible': isVisible, + if (deletedAt != null) 'deleted_at': deletedAt, + }); + } + + AssetFaceEntityCompanion copyWith({ + Value? id, + Value? assetId, + Value? personId, + Value? imageWidth, + Value? imageHeight, + Value? boundingBoxX1, + Value? boundingBoxY1, + Value? boundingBoxX2, + Value? boundingBoxY2, + Value? sourceType, + Value? isVisible, + Value? deletedAt, + }) { + return AssetFaceEntityCompanion( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + personId: personId ?? this.personId, + imageWidth: imageWidth ?? this.imageWidth, + imageHeight: imageHeight ?? this.imageHeight, + boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1, + boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1, + boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2, + boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2, + sourceType: sourceType ?? this.sourceType, + isVisible: isVisible ?? this.isVisible, + deletedAt: deletedAt ?? this.deletedAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (personId.present) { + map['person_id'] = Variable(personId.value); + } + if (imageWidth.present) { + map['image_width'] = Variable(imageWidth.value); + } + if (imageHeight.present) { + map['image_height'] = Variable(imageHeight.value); + } + if (boundingBoxX1.present) { + map['bounding_box_x1'] = Variable(boundingBoxX1.value); + } + if (boundingBoxY1.present) { + map['bounding_box_y1'] = Variable(boundingBoxY1.value); + } + if (boundingBoxX2.present) { + map['bounding_box_x2'] = Variable(boundingBoxX2.value); + } + if (boundingBoxY2.present) { + map['bounding_box_y2'] = Variable(boundingBoxY2.value); + } + if (sourceType.present) { + map['source_type'] = Variable(sourceType.value); + } + if (isVisible.present) { + map['is_visible'] = Variable(isVisible.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AssetFaceEntityCompanion(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('personId: $personId, ') + ..write('imageWidth: $imageWidth, ') + ..write('imageHeight: $imageHeight, ') + ..write('boundingBoxX1: $boundingBoxX1, ') + ..write('boundingBoxY1: $boundingBoxY1, ') + ..write('boundingBoxX2: $boundingBoxX2, ') + ..write('boundingBoxY2: $boundingBoxY2, ') + ..write('sourceType: $sourceType, ') + ..write('isVisible: $isVisible, ') + ..write('deletedAt: $deletedAt') + ..write(')')) + .toString(); + } +} + +class StoreEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + StoreEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn stringValue = GeneratedColumn( + 'string_value', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn intValue = GeneratedColumn( + 'int_value', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + @override + List get $columns => [id, stringValue, intValue]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'store_entity'; + @override + Set get $primaryKey => {id}; + @override + StoreEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return StoreEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}id'], + )!, + stringValue: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}string_value'], + ), + intValue: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}int_value'], + ), + ); + } + + @override + StoreEntity createAlias(String alias) { + return StoreEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class StoreEntityData extends DataClass implements Insertable { + final int id; + final String? stringValue; + final int? intValue; + const StoreEntityData({required this.id, this.stringValue, this.intValue}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + if (!nullToAbsent || stringValue != null) { + map['string_value'] = Variable(stringValue); + } + if (!nullToAbsent || intValue != null) { + map['int_value'] = Variable(intValue); + } + return map; + } + + factory StoreEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return StoreEntityData( + id: serializer.fromJson(json['id']), + stringValue: serializer.fromJson(json['stringValue']), + intValue: serializer.fromJson(json['intValue']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'stringValue': serializer.toJson(stringValue), + 'intValue': serializer.toJson(intValue), + }; + } + + StoreEntityData copyWith({ + int? id, + Value stringValue = const Value.absent(), + Value intValue = const Value.absent(), + }) => StoreEntityData( + id: id ?? this.id, + stringValue: stringValue.present ? stringValue.value : this.stringValue, + intValue: intValue.present ? intValue.value : this.intValue, + ); + StoreEntityData copyWithCompanion(StoreEntityCompanion data) { + return StoreEntityData( + id: data.id.present ? data.id.value : this.id, + stringValue: data.stringValue.present + ? data.stringValue.value + : this.stringValue, + intValue: data.intValue.present ? data.intValue.value : this.intValue, + ); + } + + @override + String toString() { + return (StringBuffer('StoreEntityData(') + ..write('id: $id, ') + ..write('stringValue: $stringValue, ') + ..write('intValue: $intValue') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, stringValue, intValue); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is StoreEntityData && + other.id == this.id && + other.stringValue == this.stringValue && + other.intValue == this.intValue); +} + +class StoreEntityCompanion extends UpdateCompanion { + final Value id; + final Value stringValue; + final Value intValue; + const StoreEntityCompanion({ + this.id = const Value.absent(), + this.stringValue = const Value.absent(), + this.intValue = const Value.absent(), + }); + StoreEntityCompanion.insert({ + required int id, + this.stringValue = const Value.absent(), + this.intValue = const Value.absent(), + }) : id = Value(id); + static Insertable custom({ + Expression? id, + Expression? stringValue, + Expression? intValue, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (stringValue != null) 'string_value': stringValue, + if (intValue != null) 'int_value': intValue, + }); + } + + StoreEntityCompanion copyWith({ + Value? id, + Value? stringValue, + Value? intValue, + }) { + return StoreEntityCompanion( + id: id ?? this.id, + stringValue: stringValue ?? this.stringValue, + intValue: intValue ?? this.intValue, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (stringValue.present) { + map['string_value'] = Variable(stringValue.value); + } + if (intValue.present) { + map['int_value'] = Variable(intValue.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('StoreEntityCompanion(') + ..write('id: $id, ') + ..write('stringValue: $stringValue, ') + ..write('intValue: $intValue') + ..write(')')) + .toString(); + } +} + +class TrashedLocalAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + TrashedLocalAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn durationMs = GeneratedColumn( + 'duration_ms', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0 CHECK (is_favorite IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn source = GeneratedColumn( + 'source', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn playbackStyle = GeneratedColumn( + 'playback_style', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0', + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationMs, + id, + albumId, + checksum, + isFavorite, + orientation, + source, + playbackStyle, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'trashed_local_asset_entity'; + @override + Set get $primaryKey => {id, albumId}; + @override + TrashedLocalAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return TrashedLocalAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationMs: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_ms'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_favorite'], + )!, + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}orientation'], + )!, + source: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}source'], + )!, + playbackStyle: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}playback_style'], + )!, + ); + } + + @override + TrashedLocalAssetEntity createAlias(String alias) { + return TrashedLocalAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id, album_id)']; + @override + bool get dontWriteConstraints => true; +} + +class TrashedLocalAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final String createdAt; + final String updatedAt; + final int? width; + final int? height; + final int? durationMs; + final String id; + final String albumId; + final String? checksum; + final int isFavorite; + final int orientation; + final int source; + final int playbackStyle; + const TrashedLocalAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationMs, + required this.id, + required this.albumId, + this.checksum, + required this.isFavorite, + required this.orientation, + required this.source, + required this.playbackStyle, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationMs != null) { + map['duration_ms'] = Variable(durationMs); + } + map['id'] = Variable(id); + map['album_id'] = Variable(albumId); + if (!nullToAbsent || checksum != null) { + map['checksum'] = Variable(checksum); + } + map['is_favorite'] = Variable(isFavorite); + map['orientation'] = Variable(orientation); + map['source'] = Variable(source); + map['playback_style'] = Variable(playbackStyle); + return map; + } + + factory TrashedLocalAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return TrashedLocalAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationMs: serializer.fromJson(json['durationMs']), + id: serializer.fromJson(json['id']), + albumId: serializer.fromJson(json['albumId']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + orientation: serializer.fromJson(json['orientation']), + source: serializer.fromJson(json['source']), + playbackStyle: serializer.fromJson(json['playbackStyle']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationMs': serializer.toJson(durationMs), + 'id': serializer.toJson(id), + 'albumId': serializer.toJson(albumId), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'orientation': serializer.toJson(orientation), + 'source': serializer.toJson(source), + 'playbackStyle': serializer.toJson(playbackStyle), + }; + } + + TrashedLocalAssetEntityData copyWith({ + String? name, + int? type, + String? createdAt, + String? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationMs = const Value.absent(), + String? id, + String? albumId, + Value checksum = const Value.absent(), + int? isFavorite, + int? orientation, + int? source, + int? playbackStyle, + }) => TrashedLocalAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationMs: durationMs.present ? durationMs.value : this.durationMs, + id: id ?? this.id, + albumId: albumId ?? this.albumId, + checksum: checksum.present ? checksum.value : this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + source: source ?? this.source, + playbackStyle: playbackStyle ?? this.playbackStyle, + ); + TrashedLocalAssetEntityData copyWithCompanion( + TrashedLocalAssetEntityCompanion data, + ) { + return TrashedLocalAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationMs: data.durationMs.present + ? data.durationMs.value + : this.durationMs, + id: data.id.present ? data.id.value : this.id, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + source: data.source.present ? data.source.value : this.source, + playbackStyle: data.playbackStyle.present + ? data.playbackStyle.value + : this.playbackStyle, + ); + } + + @override + String toString() { + return (StringBuffer('TrashedLocalAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationMs: $durationMs, ') + ..write('id: $id, ') + ..write('albumId: $albumId, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation, ') + ..write('source: $source, ') + ..write('playbackStyle: $playbackStyle') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationMs, + id, + albumId, + checksum, + isFavorite, + orientation, + source, + playbackStyle, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is TrashedLocalAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationMs == this.durationMs && + other.id == this.id && + other.albumId == this.albumId && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.orientation == this.orientation && + other.source == this.source && + other.playbackStyle == this.playbackStyle); +} + +class TrashedLocalAssetEntityCompanion + extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationMs; + final Value id; + final Value albumId; + final Value checksum; + final Value isFavorite; + final Value orientation; + final Value source; + final Value playbackStyle; + const TrashedLocalAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationMs = const Value.absent(), + this.id = const Value.absent(), + this.albumId = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + this.source = const Value.absent(), + this.playbackStyle = const Value.absent(), + }); + TrashedLocalAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationMs = const Value.absent(), + required String id, + required String albumId, + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + required int source, + this.playbackStyle = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id), + albumId = Value(albumId), + source = Value(source); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationMs, + Expression? id, + Expression? albumId, + Expression? checksum, + Expression? isFavorite, + Expression? orientation, + Expression? source, + Expression? playbackStyle, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationMs != null) 'duration_ms': durationMs, + if (id != null) 'id': id, + if (albumId != null) 'album_id': albumId, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (orientation != null) 'orientation': orientation, + if (source != null) 'source': source, + if (playbackStyle != null) 'playback_style': playbackStyle, + }); + } + + TrashedLocalAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationMs, + Value? id, + Value? albumId, + Value? checksum, + Value? isFavorite, + Value? orientation, + Value? source, + Value? playbackStyle, + }) { + return TrashedLocalAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationMs: durationMs ?? this.durationMs, + id: id ?? this.id, + albumId: albumId ?? this.albumId, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + source: source ?? this.source, + playbackStyle: playbackStyle ?? this.playbackStyle, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationMs.present) { + map['duration_ms'] = Variable(durationMs.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + if (source.present) { + map['source'] = Variable(source.value); + } + if (playbackStyle.present) { + map['playback_style'] = Variable(playbackStyle.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('TrashedLocalAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationMs: $durationMs, ') + ..write('id: $id, ') + ..write('albumId: $albumId, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation, ') + ..write('source: $source, ') + ..write('playbackStyle: $playbackStyle') + ..write(')')) + .toString(); + } +} + +class AssetEditEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AssetEditEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES remote_asset_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn action = GeneratedColumn( + 'action', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn parameters = + GeneratedColumn( + 'parameters', + aliasedName, + false, + type: DriftSqlType.blob, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn sequence = GeneratedColumn( + 'sequence', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + @override + List get $columns => [ + id, + assetId, + action, + parameters, + sequence, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'asset_edit_entity'; + @override + Set get $primaryKey => {id}; + @override + AssetEditEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AssetEditEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + action: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}action'], + )!, + parameters: attachedDatabase.typeMapping.read( + DriftSqlType.blob, + data['${effectivePrefix}parameters'], + )!, + sequence: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}sequence'], + )!, + ); + } + + @override + AssetEditEntity createAlias(String alias) { + return AssetEditEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class AssetEditEntityData extends DataClass + implements Insertable { + final String id; + final String assetId; + final int action; + final i2.Uint8List parameters; + final int sequence; + const AssetEditEntityData({ + required this.id, + required this.assetId, + required this.action, + required this.parameters, + required this.sequence, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['asset_id'] = Variable(assetId); + map['action'] = Variable(action); + map['parameters'] = Variable(parameters); + map['sequence'] = Variable(sequence); + return map; + } + + factory AssetEditEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AssetEditEntityData( + id: serializer.fromJson(json['id']), + assetId: serializer.fromJson(json['assetId']), + action: serializer.fromJson(json['action']), + parameters: serializer.fromJson(json['parameters']), + sequence: serializer.fromJson(json['sequence']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'assetId': serializer.toJson(assetId), + 'action': serializer.toJson(action), + 'parameters': serializer.toJson(parameters), + 'sequence': serializer.toJson(sequence), + }; + } + + AssetEditEntityData copyWith({ + String? id, + String? assetId, + int? action, + i2.Uint8List? parameters, + int? sequence, + }) => AssetEditEntityData( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + action: action ?? this.action, + parameters: parameters ?? this.parameters, + sequence: sequence ?? this.sequence, + ); + AssetEditEntityData copyWithCompanion(AssetEditEntityCompanion data) { + return AssetEditEntityData( + id: data.id.present ? data.id.value : this.id, + assetId: data.assetId.present ? data.assetId.value : this.assetId, + action: data.action.present ? data.action.value : this.action, + parameters: data.parameters.present + ? data.parameters.value + : this.parameters, + sequence: data.sequence.present ? data.sequence.value : this.sequence, + ); + } + + @override + String toString() { + return (StringBuffer('AssetEditEntityData(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('action: $action, ') + ..write('parameters: $parameters, ') + ..write('sequence: $sequence') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + assetId, + action, + $driftBlobEquality.hash(parameters), + sequence, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AssetEditEntityData && + other.id == this.id && + other.assetId == this.assetId && + other.action == this.action && + $driftBlobEquality.equals(other.parameters, this.parameters) && + other.sequence == this.sequence); +} + +class AssetEditEntityCompanion extends UpdateCompanion { + final Value id; + final Value assetId; + final Value action; + final Value parameters; + final Value sequence; + const AssetEditEntityCompanion({ + this.id = const Value.absent(), + this.assetId = const Value.absent(), + this.action = const Value.absent(), + this.parameters = const Value.absent(), + this.sequence = const Value.absent(), + }); + AssetEditEntityCompanion.insert({ + required String id, + required String assetId, + required int action, + required i2.Uint8List parameters, + required int sequence, + }) : id = Value(id), + assetId = Value(assetId), + action = Value(action), + parameters = Value(parameters), + sequence = Value(sequence); + static Insertable custom({ + Expression? id, + Expression? assetId, + Expression? action, + Expression? parameters, + Expression? sequence, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (assetId != null) 'asset_id': assetId, + if (action != null) 'action': action, + if (parameters != null) 'parameters': parameters, + if (sequence != null) 'sequence': sequence, + }); + } + + AssetEditEntityCompanion copyWith({ + Value? id, + Value? assetId, + Value? action, + Value? parameters, + Value? sequence, + }) { + return AssetEditEntityCompanion( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + action: action ?? this.action, + parameters: parameters ?? this.parameters, + sequence: sequence ?? this.sequence, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (action.present) { + map['action'] = Variable(action.value); + } + if (parameters.present) { + map['parameters'] = Variable(parameters.value); + } + if (sequence.present) { + map['sequence'] = Variable(sequence.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AssetEditEntityCompanion(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('action: $action, ') + ..write('parameters: $parameters, ') + ..write('sequence: $sequence') + ..write(')')) + .toString(); + } +} + +class Settings extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + Settings(this.attachedDatabase, [this._alias]); + late final GeneratedColumn key = GeneratedColumn( + 'key', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn value = GeneratedColumn( + 'value', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + @override + List get $columns => [key, value, updatedAt]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'settings'; + @override + Set get $primaryKey => {key}; + @override + SettingsData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return SettingsData( + key: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}key'], + )!, + value: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}value'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + ); + } + + @override + Settings createAlias(String alias) { + return Settings(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY("key")']; + @override + bool get dontWriteConstraints => true; +} + +class SettingsData extends DataClass implements Insertable { + final String key; + final String value; + final String updatedAt; + const SettingsData({ + required this.key, + required this.value, + required this.updatedAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['key'] = Variable(key); + map['value'] = Variable(value); + map['updated_at'] = Variable(updatedAt); + return map; + } + + factory SettingsData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return SettingsData( + key: serializer.fromJson(json['key']), + value: serializer.fromJson(json['value']), + updatedAt: serializer.fromJson(json['updatedAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'key': serializer.toJson(key), + 'value': serializer.toJson(value), + 'updatedAt': serializer.toJson(updatedAt), + }; + } + + SettingsData copyWith({String? key, String? value, String? updatedAt}) => + SettingsData( + key: key ?? this.key, + value: value ?? this.value, + updatedAt: updatedAt ?? this.updatedAt, + ); + SettingsData copyWithCompanion(SettingsCompanion data) { + return SettingsData( + key: data.key.present ? data.key.value : this.key, + value: data.value.present ? data.value.value : this.value, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ); + } + + @override + String toString() { + return (StringBuffer('SettingsData(') + ..write('key: $key, ') + ..write('value: $value, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(key, value, updatedAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is SettingsData && + other.key == this.key && + other.value == this.value && + other.updatedAt == this.updatedAt); +} + +class SettingsCompanion extends UpdateCompanion { + final Value key; + final Value value; + final Value updatedAt; + const SettingsCompanion({ + this.key = const Value.absent(), + this.value = const Value.absent(), + this.updatedAt = const Value.absent(), + }); + SettingsCompanion.insert({ + required String key, + required String value, + this.updatedAt = const Value.absent(), + }) : key = Value(key), + value = Value(value); + static Insertable custom({ + Expression? key, + Expression? value, + Expression? updatedAt, + }) { + return RawValuesInsertable({ + if (key != null) 'key': key, + if (value != null) 'value': value, + if (updatedAt != null) 'updated_at': updatedAt, + }); + } + + SettingsCompanion copyWith({ + Value? key, + Value? value, + Value? updatedAt, + }) { + return SettingsCompanion( + key: key ?? this.key, + value: value ?? this.value, + updatedAt: updatedAt ?? this.updatedAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (key.present) { + map['key'] = Variable(key.value); + } + if (value.present) { + map['value'] = Variable(value.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('SettingsCompanion(') + ..write('key: $key, ') + ..write('value: $value, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } +} + +class AssetOcrEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AssetOcrEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES remote_asset_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn x1 = GeneratedColumn( + 'x1', + aliasedName, + false, + type: DriftSqlType.double, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn y1 = GeneratedColumn( + 'y1', + aliasedName, + false, + type: DriftSqlType.double, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn x2 = GeneratedColumn( + 'x2', + aliasedName, + false, + type: DriftSqlType.double, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn y2 = GeneratedColumn( + 'y2', + aliasedName, + false, + type: DriftSqlType.double, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn x3 = GeneratedColumn( + 'x3', + aliasedName, + false, + type: DriftSqlType.double, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn y3 = GeneratedColumn( + 'y3', + aliasedName, + false, + type: DriftSqlType.double, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn x4 = GeneratedColumn( + 'x4', + aliasedName, + false, + type: DriftSqlType.double, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn y4 = GeneratedColumn( + 'y4', + aliasedName, + false, + type: DriftSqlType.double, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn boxScore = GeneratedColumn( + 'box_score', + aliasedName, + false, + type: DriftSqlType.double, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn textScore = GeneratedColumn( + 'text_score', + aliasedName, + false, + type: DriftSqlType.double, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn recognizedText = GeneratedColumn( + 'recognized_text', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn isVisible = GeneratedColumn( + 'is_visible', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 1 CHECK (is_visible IN (0, 1))', + defaultValue: const CustomExpression('1'), + ); + @override + List get $columns => [ + id, + assetId, + x1, + y1, + x2, + y2, + x3, + y3, + x4, + y4, + boxScore, + textScore, + recognizedText, + isVisible, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'asset_ocr_entity'; + @override + Set get $primaryKey => {id}; + @override + AssetOcrEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AssetOcrEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + x1: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}x1'], + )!, + y1: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}y1'], + )!, + x2: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}x2'], + )!, + y2: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}y2'], + )!, + x3: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}x3'], + )!, + y3: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}y3'], + )!, + x4: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}x4'], + )!, + y4: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}y4'], + )!, + boxScore: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}box_score'], + )!, + textScore: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}text_score'], + )!, + recognizedText: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}recognized_text'], + )!, + isVisible: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_visible'], + )!, + ); + } + + @override + AssetOcrEntity createAlias(String alias) { + return AssetOcrEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class AssetOcrEntityData extends DataClass + implements Insertable { + final String id; + final String assetId; + final double x1; + final double y1; + final double x2; + final double y2; + final double x3; + final double y3; + final double x4; + final double y4; + final double boxScore; + final double textScore; + final String recognizedText; + final int isVisible; + const AssetOcrEntityData({ + required this.id, + required this.assetId, + required this.x1, + required this.y1, + required this.x2, + required this.y2, + required this.x3, + required this.y3, + required this.x4, + required this.y4, + required this.boxScore, + required this.textScore, + required this.recognizedText, + required this.isVisible, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['asset_id'] = Variable(assetId); + map['x1'] = Variable(x1); + map['y1'] = Variable(y1); + map['x2'] = Variable(x2); + map['y2'] = Variable(y2); + map['x3'] = Variable(x3); + map['y3'] = Variable(y3); + map['x4'] = Variable(x4); + map['y4'] = Variable(y4); + map['box_score'] = Variable(boxScore); + map['text_score'] = Variable(textScore); + map['recognized_text'] = Variable(recognizedText); + map['is_visible'] = Variable(isVisible); + return map; + } + + factory AssetOcrEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AssetOcrEntityData( + id: serializer.fromJson(json['id']), + assetId: serializer.fromJson(json['assetId']), + x1: serializer.fromJson(json['x1']), + y1: serializer.fromJson(json['y1']), + x2: serializer.fromJson(json['x2']), + y2: serializer.fromJson(json['y2']), + x3: serializer.fromJson(json['x3']), + y3: serializer.fromJson(json['y3']), + x4: serializer.fromJson(json['x4']), + y4: serializer.fromJson(json['y4']), + boxScore: serializer.fromJson(json['boxScore']), + textScore: serializer.fromJson(json['textScore']), + recognizedText: serializer.fromJson(json['recognizedText']), + isVisible: serializer.fromJson(json['isVisible']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'assetId': serializer.toJson(assetId), + 'x1': serializer.toJson(x1), + 'y1': serializer.toJson(y1), + 'x2': serializer.toJson(x2), + 'y2': serializer.toJson(y2), + 'x3': serializer.toJson(x3), + 'y3': serializer.toJson(y3), + 'x4': serializer.toJson(x4), + 'y4': serializer.toJson(y4), + 'boxScore': serializer.toJson(boxScore), + 'textScore': serializer.toJson(textScore), + 'recognizedText': serializer.toJson(recognizedText), + 'isVisible': serializer.toJson(isVisible), + }; + } + + AssetOcrEntityData copyWith({ + String? id, + String? assetId, + double? x1, + double? y1, + double? x2, + double? y2, + double? x3, + double? y3, + double? x4, + double? y4, + double? boxScore, + double? textScore, + String? recognizedText, + int? isVisible, + }) => AssetOcrEntityData( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + x1: x1 ?? this.x1, + y1: y1 ?? this.y1, + x2: x2 ?? this.x2, + y2: y2 ?? this.y2, + x3: x3 ?? this.x3, + y3: y3 ?? this.y3, + x4: x4 ?? this.x4, + y4: y4 ?? this.y4, + boxScore: boxScore ?? this.boxScore, + textScore: textScore ?? this.textScore, + recognizedText: recognizedText ?? this.recognizedText, + isVisible: isVisible ?? this.isVisible, + ); + AssetOcrEntityData copyWithCompanion(AssetOcrEntityCompanion data) { + return AssetOcrEntityData( + id: data.id.present ? data.id.value : this.id, + assetId: data.assetId.present ? data.assetId.value : this.assetId, + x1: data.x1.present ? data.x1.value : this.x1, + y1: data.y1.present ? data.y1.value : this.y1, + x2: data.x2.present ? data.x2.value : this.x2, + y2: data.y2.present ? data.y2.value : this.y2, + x3: data.x3.present ? data.x3.value : this.x3, + y3: data.y3.present ? data.y3.value : this.y3, + x4: data.x4.present ? data.x4.value : this.x4, + y4: data.y4.present ? data.y4.value : this.y4, + boxScore: data.boxScore.present ? data.boxScore.value : this.boxScore, + textScore: data.textScore.present ? data.textScore.value : this.textScore, + recognizedText: data.recognizedText.present + ? data.recognizedText.value + : this.recognizedText, + isVisible: data.isVisible.present ? data.isVisible.value : this.isVisible, + ); + } + + @override + String toString() { + return (StringBuffer('AssetOcrEntityData(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('x1: $x1, ') + ..write('y1: $y1, ') + ..write('x2: $x2, ') + ..write('y2: $y2, ') + ..write('x3: $x3, ') + ..write('y3: $y3, ') + ..write('x4: $x4, ') + ..write('y4: $y4, ') + ..write('boxScore: $boxScore, ') + ..write('textScore: $textScore, ') + ..write('recognizedText: $recognizedText, ') + ..write('isVisible: $isVisible') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + assetId, + x1, + y1, + x2, + y2, + x3, + y3, + x4, + y4, + boxScore, + textScore, + recognizedText, + isVisible, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AssetOcrEntityData && + other.id == this.id && + other.assetId == this.assetId && + other.x1 == this.x1 && + other.y1 == this.y1 && + other.x2 == this.x2 && + other.y2 == this.y2 && + other.x3 == this.x3 && + other.y3 == this.y3 && + other.x4 == this.x4 && + other.y4 == this.y4 && + other.boxScore == this.boxScore && + other.textScore == this.textScore && + other.recognizedText == this.recognizedText && + other.isVisible == this.isVisible); +} + +class AssetOcrEntityCompanion extends UpdateCompanion { + final Value id; + final Value assetId; + final Value x1; + final Value y1; + final Value x2; + final Value y2; + final Value x3; + final Value y3; + final Value x4; + final Value y4; + final Value boxScore; + final Value textScore; + final Value recognizedText; + final Value isVisible; + const AssetOcrEntityCompanion({ + this.id = const Value.absent(), + this.assetId = const Value.absent(), + this.x1 = const Value.absent(), + this.y1 = const Value.absent(), + this.x2 = const Value.absent(), + this.y2 = const Value.absent(), + this.x3 = const Value.absent(), + this.y3 = const Value.absent(), + this.x4 = const Value.absent(), + this.y4 = const Value.absent(), + this.boxScore = const Value.absent(), + this.textScore = const Value.absent(), + this.recognizedText = const Value.absent(), + this.isVisible = const Value.absent(), + }); + AssetOcrEntityCompanion.insert({ + required String id, + required String assetId, + required double x1, + required double y1, + required double x2, + required double y2, + required double x3, + required double y3, + required double x4, + required double y4, + required double boxScore, + required double textScore, + required String recognizedText, + this.isVisible = const Value.absent(), + }) : id = Value(id), + assetId = Value(assetId), + x1 = Value(x1), + y1 = Value(y1), + x2 = Value(x2), + y2 = Value(y2), + x3 = Value(x3), + y3 = Value(y3), + x4 = Value(x4), + y4 = Value(y4), + boxScore = Value(boxScore), + textScore = Value(textScore), + recognizedText = Value(recognizedText); + static Insertable custom({ + Expression? id, + Expression? assetId, + Expression? x1, + Expression? y1, + Expression? x2, + Expression? y2, + Expression? x3, + Expression? y3, + Expression? x4, + Expression? y4, + Expression? boxScore, + Expression? textScore, + Expression? recognizedText, + Expression? isVisible, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (assetId != null) 'asset_id': assetId, + if (x1 != null) 'x1': x1, + if (y1 != null) 'y1': y1, + if (x2 != null) 'x2': x2, + if (y2 != null) 'y2': y2, + if (x3 != null) 'x3': x3, + if (y3 != null) 'y3': y3, + if (x4 != null) 'x4': x4, + if (y4 != null) 'y4': y4, + if (boxScore != null) 'box_score': boxScore, + if (textScore != null) 'text_score': textScore, + if (recognizedText != null) 'recognized_text': recognizedText, + if (isVisible != null) 'is_visible': isVisible, + }); + } + + AssetOcrEntityCompanion copyWith({ + Value? id, + Value? assetId, + Value? x1, + Value? y1, + Value? x2, + Value? y2, + Value? x3, + Value? y3, + Value? x4, + Value? y4, + Value? boxScore, + Value? textScore, + Value? recognizedText, + Value? isVisible, + }) { + return AssetOcrEntityCompanion( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + x1: x1 ?? this.x1, + y1: y1 ?? this.y1, + x2: x2 ?? this.x2, + y2: y2 ?? this.y2, + x3: x3 ?? this.x3, + y3: y3 ?? this.y3, + x4: x4 ?? this.x4, + y4: y4 ?? this.y4, + boxScore: boxScore ?? this.boxScore, + textScore: textScore ?? this.textScore, + recognizedText: recognizedText ?? this.recognizedText, + isVisible: isVisible ?? this.isVisible, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (x1.present) { + map['x1'] = Variable(x1.value); + } + if (y1.present) { + map['y1'] = Variable(y1.value); + } + if (x2.present) { + map['x2'] = Variable(x2.value); + } + if (y2.present) { + map['y2'] = Variable(y2.value); + } + if (x3.present) { + map['x3'] = Variable(x3.value); + } + if (y3.present) { + map['y3'] = Variable(y3.value); + } + if (x4.present) { + map['x4'] = Variable(x4.value); + } + if (y4.present) { + map['y4'] = Variable(y4.value); + } + if (boxScore.present) { + map['box_score'] = Variable(boxScore.value); + } + if (textScore.present) { + map['text_score'] = Variable(textScore.value); + } + if (recognizedText.present) { + map['recognized_text'] = Variable(recognizedText.value); + } + if (isVisible.present) { + map['is_visible'] = Variable(isVisible.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AssetOcrEntityCompanion(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('x1: $x1, ') + ..write('y1: $y1, ') + ..write('x2: $x2, ') + ..write('y2: $y2, ') + ..write('x3: $x3, ') + ..write('y3: $y3, ') + ..write('x4: $x4, ') + ..write('y4: $y4, ') + ..write('boxScore: $boxScore, ') + ..write('textScore: $textScore, ') + ..write('recognizedText: $recognizedText, ') + ..write('isVisible: $isVisible') + ..write(')')) + .toString(); + } +} + +class DatabaseAtV29 extends GeneratedDatabase { + DatabaseAtV29(QueryExecutor e) : super(e); + late final UserEntity userEntity = UserEntity(this); + late final RemoteAssetEntity remoteAssetEntity = RemoteAssetEntity(this); + late final StackEntity stackEntity = StackEntity(this); + late final LocalAssetEntity localAssetEntity = LocalAssetEntity(this); + late final RemoteAlbumEntity remoteAlbumEntity = RemoteAlbumEntity(this); + late final LocalAlbumEntity localAlbumEntity = LocalAlbumEntity(this); + late final LocalAlbumAssetEntity localAlbumAssetEntity = + LocalAlbumAssetEntity(this); + late final Index idxLocalAlbumAssetAlbumAsset = Index( + 'idx_local_album_asset_album_asset', + 'CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)', + ); + late final Index idxLocalAssetChecksum = Index( + 'idx_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)', + ); + late final Index idxLocalAssetCloudId = Index( + 'idx_local_asset_cloud_id', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)', + ); + late final Index idxLocalAssetCreatedAt = Index( + 'idx_local_asset_created_at', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_created_at ON local_asset_entity (created_at)', + ); + late final Index idxStackPrimaryAssetId = Index( + 'idx_stack_primary_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)', + ); + late final Index uQRemoteAssetsOwnerChecksum = Index( + 'UQ_remote_assets_owner_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)', + ); + late final Index uQRemoteAssetsOwnerLibraryChecksum = Index( + 'UQ_remote_assets_owner_library_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)', + ); + late final Index idxRemoteAssetChecksum = Index( + 'idx_remote_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)', + ); + late final Index idxRemoteAssetStackId = Index( + 'idx_remote_asset_stack_id', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)', + ); + late final Index idxRemoteAssetOwnerVisibilityDeletedCreated = Index( + 'idx_remote_asset_owner_visibility_deleted_created', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_visibility_deleted_created ON remote_asset_entity (owner_id, visibility, deleted_at, created_at DESC)', + ); + late final AuthUserEntity authUserEntity = AuthUserEntity(this); + late final UserMetadataEntity userMetadataEntity = UserMetadataEntity(this); + late final PartnerEntity partnerEntity = PartnerEntity(this); + late final RemoteExifEntity remoteExifEntity = RemoteExifEntity(this); + late final RemoteAlbumAssetEntity remoteAlbumAssetEntity = + RemoteAlbumAssetEntity(this); + late final RemoteAlbumUserEntity remoteAlbumUserEntity = + RemoteAlbumUserEntity(this); + late final RemoteAssetCloudIdEntity remoteAssetCloudIdEntity = + RemoteAssetCloudIdEntity(this); + late final MemoryEntity memoryEntity = MemoryEntity(this); + late final MemoryAssetEntity memoryAssetEntity = MemoryAssetEntity(this); + late final PersonEntity personEntity = PersonEntity(this); + late final AssetFaceEntity assetFaceEntity = AssetFaceEntity(this); + late final StoreEntity storeEntity = StoreEntity(this); + late final TrashedLocalAssetEntity trashedLocalAssetEntity = + TrashedLocalAssetEntity(this); + late final AssetEditEntity assetEditEntity = AssetEditEntity(this); + late final Settings settings = Settings(this); + late final AssetOcrEntity assetOcrEntity = AssetOcrEntity(this); + late final Index idxPartnerSharedWithId = Index( + 'idx_partner_shared_with_id', + 'CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)', + ); + late final Index idxLatLng = Index( + 'idx_lat_lng', + 'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)', + ); + late final Index idxRemoteExifCity = Index( + 'idx_remote_exif_city', + 'CREATE INDEX IF NOT EXISTS idx_remote_exif_city ON remote_exif_entity (city) WHERE city IS NOT NULL', + ); + late final Index idxRemoteAlbumAssetAlbumAsset = Index( + 'idx_remote_album_asset_album_asset', + 'CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)', + ); + late final Index idxRemoteAssetCloudId = Index( + 'idx_remote_asset_cloud_id', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)', + ); + late final Index idxPersonOwnerId = Index( + 'idx_person_owner_id', + 'CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)', + ); + late final Index idxAssetFacePersonId = Index( + 'idx_asset_face_person_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)', + ); + late final Index idxAssetFaceAssetId = Index( + 'idx_asset_face_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)', + ); + late final Index idxAssetFaceVisiblePerson = Index( + 'idx_asset_face_visible_person', + 'CREATE INDEX IF NOT EXISTS idx_asset_face_visible_person ON asset_face_entity (person_id, asset_id) WHERE is_visible = 1 AND deleted_at IS NULL', + ); + late final Index idxTrashedLocalAssetChecksum = Index( + 'idx_trashed_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)', + ); + late final Index idxTrashedLocalAssetAlbum = Index( + 'idx_trashed_local_asset_album', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)', + ); + late final Index idxAssetEditAssetId = Index( + 'idx_asset_edit_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_edit_asset_id ON asset_edit_entity (asset_id)', + ); + late final Index idxAssetOcrAssetId = Index( + 'idx_asset_ocr_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_ocr_asset_id ON asset_ocr_entity (asset_id)', + ); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [ + userEntity, + remoteAssetEntity, + stackEntity, + localAssetEntity, + remoteAlbumEntity, + localAlbumEntity, + localAlbumAssetEntity, + idxLocalAlbumAssetAlbumAsset, + idxLocalAssetChecksum, + idxLocalAssetCloudId, + idxLocalAssetCreatedAt, + idxStackPrimaryAssetId, + uQRemoteAssetsOwnerChecksum, + uQRemoteAssetsOwnerLibraryChecksum, + idxRemoteAssetChecksum, + idxRemoteAssetStackId, + idxRemoteAssetOwnerVisibilityDeletedCreated, + authUserEntity, + userMetadataEntity, + partnerEntity, + remoteExifEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + remoteAssetCloudIdEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + assetFaceEntity, + storeEntity, + trashedLocalAssetEntity, + assetEditEntity, + settings, + assetOcrEntity, + idxPartnerSharedWithId, + idxLatLng, + idxRemoteExifCity, + idxRemoteAlbumAssetAlbumAsset, + idxRemoteAssetCloudId, + idxPersonOwnerId, + idxAssetFacePersonId, + idxAssetFaceAssetId, + idxAssetFaceVisiblePerson, + idxTrashedLocalAssetChecksum, + idxTrashedLocalAssetAlbum, + idxAssetEditAssetId, + idxAssetOcrAssetId, + ]; + @override + StreamQueryUpdateRules get streamUpdateRules => const StreamQueryUpdateRules([ + WritePropagation( + on: TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('remote_asset_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('stack_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_asset_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('remote_album_entity', kind: UpdateKind.update)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_album_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('local_album_entity', kind: UpdateKind.update)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'local_asset_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [ + TableUpdate('local_album_asset_entity', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'local_album_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [ + TableUpdate('local_album_asset_entity', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('user_metadata_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('partner_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('partner_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_asset_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('remote_exif_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_asset_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [ + TableUpdate('remote_album_asset_entity', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_album_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [ + TableUpdate('remote_album_asset_entity', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_album_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [ + TableUpdate('remote_album_user_entity', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [ + TableUpdate('remote_album_user_entity', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_asset_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [ + TableUpdate('remote_asset_cloud_id_entity', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('memory_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_asset_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('memory_asset_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'memory_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('memory_asset_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('person_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_asset_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('asset_face_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'person_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('asset_face_entity', kind: UpdateKind.update)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_asset_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('asset_edit_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_asset_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('asset_ocr_entity', kind: UpdateKind.delete)], + ), + ]); + @override + int get schemaVersion => 29; + @override + DriftDatabaseOptions get options => + const DriftDatabaseOptions(storeDateTimeAsText: true); +} diff --git a/mobile/test/fixtures/sync_stream.stub.dart b/mobile/test/fixtures/sync_stream.stub.dart index c2254c0a03..1691fbdc49 100644 --- a/mobile/test/fixtures/sync_stream.stub.dart +++ b/mobile/test/fixtures/sync_stream.stub.dart @@ -9,7 +9,7 @@ abstract final class SyncStreamStub { email: "admin@admin", id: "1", name: "Admin", - avatarColor: null, + avatarColor: const Optional.absent(), hasProfileImage: false, profileChangedAt: DateTime(2025), ), @@ -22,7 +22,7 @@ abstract final class SyncStreamStub { email: "user@user", id: "5", name: "User", - avatarColor: null, + avatarColor: const Optional.absent(), hasProfileImage: false, profileChangedAt: DateTime(2025), ), @@ -115,6 +115,7 @@ abstract final class SyncStreamStub { duration: '0', fileCreatedAt: DateTime(2025), fileModifiedAt: DateTime(2025, 1, 2), + createdAt: DateTime(2025, 1, 2), id: id, isFavorite: false, libraryId: null, diff --git a/mobile/test/infrastructure/repositories/merged_asset_drift_test.dart b/mobile/test/infrastructure/repositories/merged_asset_drift_test.dart index a25a9d92a7..a5fe6f35c4 100644 --- a/mobile/test/infrastructure/repositories/merged_asset_drift_test.dart +++ b/mobile/test/infrastructure/repositories/merged_asset_drift_test.dart @@ -38,6 +38,7 @@ void main() { visibility: AssetVisibility.timeline, createdAt: Value(createdAt), updatedAt: Value(createdAt), + uploadedAt: Value(createdAt), localDateTime: const Value(null), ), ); diff --git a/mobile/test/infrastructure/repositories/store_repository_test.dart b/mobile/test/infrastructure/repositories/store_repository_test.dart index 4cf1adc6b1..3e160c29ca 100644 --- a/mobile/test/infrastructure/repositories/store_repository_test.dart +++ b/mobile/test/infrastructure/repositories/store_repository_test.dart @@ -12,9 +12,8 @@ import 'package:immich_mobile/infrastructure/repositories/store.repository.dart' import '../../fixtures/user.stub.dart'; const _kTestAccessToken = "#TestToken"; -final _kTestBackupFailed = DateTime(2025, 2, 20, 11, 45); const _kTestVersion = 10; -const _kTestColorfulInterface = false; +const _kTestAdvancedTroubleshooting = false; final _kTestUser = UserStub.admin; Future _populateStore(Drift db) async { @@ -22,16 +21,8 @@ Future _populateStore(Drift db) async { batch.insert( db.storeEntity, StoreEntityCompanion( - id: Value(StoreKey.colorfulInterface.id), - intValue: const Value(_kTestColorfulInterface ? 1 : 0), - stringValue: const Value(null), - ), - ); - batch.insert( - db.storeEntity, - StoreEntityCompanion( - id: Value(StoreKey.backupFailedSince.id), - intValue: Value(_kTestBackupFailed.millisecondsSinceEpoch), + id: Value(StoreKey.advancedTroubleshooting.id), + intValue: const Value(_kTestAdvancedTroubleshooting ? 1 : 0), stringValue: const Value(null), ), ); @@ -84,20 +75,12 @@ void main() { expect(accessToken, _kTestAccessToken); }); - test('converts datetime', () async { - DateTime? backupFailedSince = await sut.tryGet(StoreKey.backupFailedSince); - expect(backupFailedSince, isNull); - await sut.upsert(StoreKey.backupFailedSince, _kTestBackupFailed); - backupFailedSince = await sut.tryGet(StoreKey.backupFailedSince); - expect(backupFailedSince, _kTestBackupFailed); - }); - test('converts bool', () async { - bool? colorfulInterface = await sut.tryGet(StoreKey.colorfulInterface); - expect(colorfulInterface, isNull); - await sut.upsert(StoreKey.colorfulInterface, _kTestColorfulInterface); - colorfulInterface = await sut.tryGet(StoreKey.colorfulInterface); - expect(colorfulInterface, _kTestColorfulInterface); + bool? advancedTroubleshooting = await sut.tryGet(StoreKey.advancedTroubleshooting); + expect(advancedTroubleshooting, isNull); + await sut.upsert(StoreKey.advancedTroubleshooting, _kTestAdvancedTroubleshooting); + advancedTroubleshooting = await sut.tryGet(StoreKey.advancedTroubleshooting); + expect(advancedTroubleshooting, _kTestAdvancedTroubleshooting); }); test('converts user', () async { @@ -115,11 +98,11 @@ void main() { }); test('delete()', () async { - bool? isColorful = await sut.tryGet(StoreKey.colorfulInterface); - expect(isColorful, isFalse); - await sut.delete(StoreKey.colorfulInterface); - isColorful = await sut.tryGet(StoreKey.colorfulInterface); - expect(isColorful, isNull); + bool? advancedTroubleshooting = await sut.tryGet(StoreKey.advancedTroubleshooting); + expect(advancedTroubleshooting, isFalse); + await sut.delete(StoreKey.advancedTroubleshooting); + advancedTroubleshooting = await sut.tryGet(StoreKey.advancedTroubleshooting); + expect(advancedTroubleshooting, isNull); }); test('deleteAll()', () async { @@ -164,15 +147,13 @@ void main() { emitsInOrder([ [ const StoreDto(StoreKey.version, _kTestVersion), - StoreDto(StoreKey.backupFailedSince, _kTestBackupFailed), const StoreDto(StoreKey.accessToken, _kTestAccessToken), - const StoreDto(StoreKey.colorfulInterface, _kTestColorfulInterface), + const StoreDto(StoreKey.advancedTroubleshooting, _kTestAdvancedTroubleshooting), ], [ const StoreDto(StoreKey.version, _kTestVersion + 10), - StoreDto(StoreKey.backupFailedSince, _kTestBackupFailed), const StoreDto(StoreKey.accessToken, _kTestAccessToken), - const StoreDto(StoreKey.colorfulInterface, _kTestColorfulInterface), + const StoreDto(StoreKey.advancedTroubleshooting, _kTestAdvancedTroubleshooting), ], ]), ), diff --git a/mobile/test/infrastructure/repository.mock.dart b/mobile/test/infrastructure/repository.mock.dart index b7992c1822..9c1cdae416 100644 --- a/mobile/test/infrastructure/repository.mock.dart +++ b/mobile/test/infrastructure/repository.mock.dart @@ -2,6 +2,7 @@ import 'package:immich_mobile/infrastructure/repositories/backup.repository.dart import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/log.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/remote_album.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/remote_asset.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart'; @@ -17,6 +18,8 @@ import 'package:mocktail/mocktail.dart'; class MockDriftStoreRepository extends Mock implements DriftStoreRepository {} +class MockSettingsRepository extends Mock implements SettingsRepository {} + class MockLogRepository extends Mock implements LogRepository {} class MockSyncStreamRepository extends Mock implements SyncStreamRepository {} diff --git a/mobile/test/medium/repositories/remote_album_repository_test.dart b/mobile/test/medium/repositories/remote_album_repository_test.dart index 1ae994f68b..e4d803a51c 100644 --- a/mobile/test/medium/repositories/remote_album_repository_test.dart +++ b/mobile/test/medium/repositories/remote_album_repository_test.dart @@ -17,6 +17,33 @@ void main() { await ctx.dispose(); }); + group('addAssets', () { + test('sets the first added asset as thumbnail when the album has no thumbnail', () async { + final user = await ctx.newUser(); + final album = await ctx.newRemoteAlbum(ownerId: user.id); + final asset = await ctx.newRemoteAsset(ownerId: user.id); + + await sut.addAssets(album.id, [asset.id]); + + final updated = await sut.get(album.id); + expect(updated?.thumbnailAssetId, asset.id); + expect(updated?.assetCount, 1); + }); + + test('preserves an existing thumbnail when adding assets', () async { + final user = await ctx.newUser(); + final thumbnail = await ctx.newRemoteAsset(ownerId: user.id); + final album = await ctx.newRemoteAlbum(ownerId: user.id, thumbnailAssetId: thumbnail.id); + final asset = await ctx.newRemoteAsset(ownerId: user.id); + + await sut.addAssets(album.id, [asset.id]); + + final updated = await sut.get(album.id); + expect(updated?.thumbnailAssetId, thumbnail.id); + expect(updated?.assetCount, 1); + }); + }); + group('getSortedAlbumIds', () { late String userId; @@ -33,7 +60,7 @@ void main() { test('returns single album when only one album exists', () async { final album = await ctx.newRemoteAlbum(ownerId: userId); final asset = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 1)); - await ctx.insertRemoteAlbumAsset(albumId: album.id, assetId: asset.id); + await ctx.newRemoteAlbumAsset(albumId: album.id, assetId: asset.id); final result = await sut.getSortedAlbumIds([album.id], aggregation: AssetDateAggregation.start); expect(result, [album.id]); @@ -44,22 +71,22 @@ void main() { final album1 = await ctx.newRemoteAlbum(ownerId: userId); final asset1 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 10)); final asset2 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 20)); - await ctx.insertRemoteAlbumAsset(albumId: album1.id, assetId: asset1.id); - await ctx.insertRemoteAlbumAsset(albumId: album1.id, assetId: asset2.id); + await ctx.newRemoteAlbumAsset(albumId: album1.id, assetId: asset1.id); + await ctx.newRemoteAlbumAsset(albumId: album1.id, assetId: asset2.id); // Album 2: Assets from Jan 5 to Jan 15 (start: Jan 5) final album2 = await ctx.newRemoteAlbum(ownerId: userId); final asset3 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 5)); final asset4 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 15)); - await ctx.insertRemoteAlbumAsset(albumId: album2.id, assetId: asset3.id); - await ctx.insertRemoteAlbumAsset(albumId: album2.id, assetId: asset4.id); + await ctx.newRemoteAlbumAsset(albumId: album2.id, assetId: asset3.id); + await ctx.newRemoteAlbumAsset(albumId: album2.id, assetId: asset4.id); // Album 3: Assets from Jan 25 to Jan 30 (start: Jan 25) final album3 = await ctx.newRemoteAlbum(ownerId: userId); final asset5 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 25)); final asset6 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 30)); - await ctx.insertRemoteAlbumAsset(albumId: album3.id, assetId: asset5.id); - await ctx.insertRemoteAlbumAsset(albumId: album3.id, assetId: asset6.id); + await ctx.newRemoteAlbumAsset(albumId: album3.id, assetId: asset5.id); + await ctx.newRemoteAlbumAsset(albumId: album3.id, assetId: asset6.id); final result = await sut.getSortedAlbumIds([ album1.id, @@ -76,22 +103,22 @@ void main() { final album1 = await ctx.newRemoteAlbum(ownerId: userId); final asset1 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 10)); final asset2 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 20)); - await ctx.insertRemoteAlbumAsset(albumId: album1.id, assetId: asset1.id); - await ctx.insertRemoteAlbumAsset(albumId: album1.id, assetId: asset2.id); + await ctx.newRemoteAlbumAsset(albumId: album1.id, assetId: asset1.id); + await ctx.newRemoteAlbumAsset(albumId: album1.id, assetId: asset2.id); // Album 2: Assets from Jan 5 to Jan 15 (end: Jan 15) final album2 = await ctx.newRemoteAlbum(ownerId: userId); final asset3 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 5)); final asset4 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 15)); - await ctx.insertRemoteAlbumAsset(albumId: album2.id, assetId: asset3.id); - await ctx.insertRemoteAlbumAsset(albumId: album2.id, assetId: asset4.id); + await ctx.newRemoteAlbumAsset(albumId: album2.id, assetId: asset3.id); + await ctx.newRemoteAlbumAsset(albumId: album2.id, assetId: asset4.id); // Album 3: Assets from Jan 25 to Jan 30 (end: Jan 30) final album3 = await ctx.newRemoteAlbum(ownerId: userId); final asset5 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 25)); final asset6 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 30)); - await ctx.insertRemoteAlbumAsset(albumId: album3.id, assetId: asset5.id); - await ctx.insertRemoteAlbumAsset(albumId: album3.id, assetId: asset6.id); + await ctx.newRemoteAlbumAsset(albumId: album3.id, assetId: asset5.id); + await ctx.newRemoteAlbumAsset(albumId: album3.id, assetId: asset6.id); final result = await sut.getSortedAlbumIds([ album1.id, @@ -106,11 +133,11 @@ void main() { test('handles albums with single asset', () async { final album1 = await ctx.newRemoteAlbum(ownerId: userId); final asset1 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 15)); - await ctx.insertRemoteAlbumAsset(albumId: album1.id, assetId: asset1.id); + await ctx.newRemoteAlbumAsset(albumId: album1.id, assetId: asset1.id); final album2 = await ctx.newRemoteAlbum(ownerId: userId); final asset2 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 10)); - await ctx.insertRemoteAlbumAsset(albumId: album2.id, assetId: asset2.id); + await ctx.newRemoteAlbumAsset(albumId: album2.id, assetId: asset2.id); final result = await sut.getSortedAlbumIds([album1.id, album2.id], aggregation: AssetDateAggregation.start); @@ -121,15 +148,15 @@ void main() { // Create 3 albums final album1 = await ctx.newRemoteAlbum(ownerId: userId); final asset1 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 10)); - await ctx.insertRemoteAlbumAsset(albumId: album1.id, assetId: asset1.id); + await ctx.newRemoteAlbumAsset(albumId: album1.id, assetId: asset1.id); final album2 = await ctx.newRemoteAlbum(ownerId: userId); final asset2 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 5)); - await ctx.insertRemoteAlbumAsset(albumId: album2.id, assetId: asset2.id); + await ctx.newRemoteAlbumAsset(albumId: album2.id, assetId: asset2.id); final album3 = await ctx.newRemoteAlbum(ownerId: userId); final asset3 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 15)); - await ctx.insertRemoteAlbumAsset(albumId: album3.id, assetId: asset3.id); + await ctx.newRemoteAlbumAsset(albumId: album3.id, assetId: asset3.id); // Only request album1 and album3 final result = await sut.getSortedAlbumIds([album1.id, album3.id], aggregation: AssetDateAggregation.start); @@ -143,11 +170,11 @@ void main() { final album1 = await ctx.newRemoteAlbum(ownerId: userId); final asset1 = await ctx.newRemoteAsset(ownerId: userId, createdAt: sameDate); - await ctx.insertRemoteAlbumAsset(albumId: album1.id, assetId: asset1.id); + await ctx.newRemoteAlbumAsset(albumId: album1.id, assetId: asset1.id); final album2 = await ctx.newRemoteAlbum(ownerId: userId); final asset2 = await ctx.newRemoteAsset(ownerId: userId, createdAt: sameDate); - await ctx.insertRemoteAlbumAsset(albumId: album2.id, assetId: asset2.id); + await ctx.newRemoteAlbumAsset(albumId: album2.id, assetId: asset2.id); final result = await sut.getSortedAlbumIds([album1.id, album2.id], aggregation: AssetDateAggregation.start); @@ -159,15 +186,15 @@ void main() { test('handles albums across different years', () async { final album1 = await ctx.newRemoteAlbum(ownerId: userId); final asset1 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2023, 12, 25)); - await ctx.insertRemoteAlbumAsset(albumId: album1.id, assetId: asset1.id); + await ctx.newRemoteAlbumAsset(albumId: album1.id, assetId: asset1.id); final album2 = await ctx.newRemoteAlbum(ownerId: userId); final asset2 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 5)); - await ctx.insertRemoteAlbumAsset(albumId: album2.id, assetId: asset2.id); + await ctx.newRemoteAlbumAsset(albumId: album2.id, assetId: asset2.id); final album3 = await ctx.newRemoteAlbum(ownerId: userId); final asset3 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2025, 1, 1)); - await ctx.insertRemoteAlbumAsset(albumId: album3.id, assetId: asset3.id); + await ctx.newRemoteAlbumAsset(albumId: album3.id, assetId: asset3.id); final result = await sut.getSortedAlbumIds([ album1.id, @@ -186,15 +213,15 @@ void main() { final asset3 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 15)); final asset4 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 20)); final asset5 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 25)); - await ctx.insertRemoteAlbumAsset(albumId: album1.id, assetId: asset1.id); - await ctx.insertRemoteAlbumAsset(albumId: album1.id, assetId: asset2.id); - await ctx.insertRemoteAlbumAsset(albumId: album1.id, assetId: asset3.id); - await ctx.insertRemoteAlbumAsset(albumId: album1.id, assetId: asset4.id); - await ctx.insertRemoteAlbumAsset(albumId: album1.id, assetId: asset5.id); + await ctx.newRemoteAlbumAsset(albumId: album1.id, assetId: asset1.id); + await ctx.newRemoteAlbumAsset(albumId: album1.id, assetId: asset2.id); + await ctx.newRemoteAlbumAsset(albumId: album1.id, assetId: asset3.id); + await ctx.newRemoteAlbumAsset(albumId: album1.id, assetId: asset4.id); + await ctx.newRemoteAlbumAsset(albumId: album1.id, assetId: asset5.id); final album2 = await ctx.newRemoteAlbum(ownerId: userId); final asset6 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 1)); - await ctx.insertRemoteAlbumAsset(albumId: album2.id, assetId: asset6.id); + await ctx.newRemoteAlbumAsset(albumId: album2.id, assetId: asset6.id); final resultStart = await sut.getSortedAlbumIds([album1.id, album2.id], aggregation: AssetDateAggregation.start); diff --git a/mobile/test/medium/repositories/settings_repository_test.dart b/mobile/test/medium/repositories/settings_repository_test.dart new file mode 100644 index 0000000000..6a3f79badb --- /dev/null +++ b/mobile/test/medium/repositories/settings_repository_test.dart @@ -0,0 +1,124 @@ +import 'package:drift/drift.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:immich_mobile/domain/models/log.model.dart'; +import 'package:immich_mobile/domain/models/settings_key.dart'; +import 'package:immich_mobile/infrastructure/entities/settings.entity.drift.dart'; +import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart'; + +import '../repository_context.dart'; + +void main() { + late MediumRepositoryContext ctx; + late SettingsRepository sut; + + setUpAll(() async { + ctx = MediumRepositoryContext(); + sut = await SettingsRepository.ensureInitialized(ctx.db); + }); + + tearDownAll(() async { + await ctx.dispose(); + }); + + setUp(() async { + await ctx.db.delete(ctx.db.settingsEntity).go(); + await SettingsRepository.instance.refresh(); + }); + + group('defaults', () { + test('appConfig returns key defaults when DB is empty', () { + expect(sut.appConfig.theme.mode, ThemeMode.system); + }); + + test('appConfig returns key defaults when DB is empty', () { + expect(sut.appConfig.logLevel, LogLevel.info); + }); + }); + + group('write', () { + test('persists a value and reflects it in the composed view', () async { + await sut.write(.themeMode, ThemeMode.dark); + expect(sut.appConfig.theme.mode, ThemeMode.dark); + }); + + test('persists across domains independently', () async { + await sut.write(.themeMode, ThemeMode.light); + await sut.write(.logLevel, LogLevel.severe); + expect(sut.appConfig.theme.mode, ThemeMode.light); + expect(sut.appConfig.logLevel, LogLevel.severe); + }); + + test('removes the row and reverts to default', () async { + await sut.write(.themeMode, ThemeMode.dark); + expect(sut.appConfig.theme.mode, ThemeMode.dark); + + await sut.write(.themeMode, ThemeMode.system); + expect(sut.appConfig.theme.mode, ThemeMode.system); + + final rows = await ctx.db.select(ctx.db.settingsEntity).get(); + expect(rows, isEmpty); + }); + }); + + group('delete', () {}); + + group('sync', () { + test('picks up rows that were inserted directly into the DB', () async { + await ctx.db + .into(ctx.db.settingsEntity) + .insert( + SettingsEntityCompanion.insert( + key: SettingsKey.themeMode.name, + value: ThemeMode.dark.name, + updatedAt: Value(DateTime.now()), + ), + ); + + // Cache hasn't seen this row yet โ€” view still returns the default. + expect(sut.appConfig.theme.mode, ThemeMode.system); + + await SettingsRepository.instance.refresh(); + expect(sut.appConfig.theme.mode, ThemeMode.dark); + }); + + test('drops cached values for rows that were deleted out from under the repo', () async { + await sut.write(.themeMode, ThemeMode.dark); + // Wipe the row directly. Cache still holds the old value. + await ctx.db.delete(ctx.db.settingsEntity).go(); + expect(sut.appConfig.theme.mode, ThemeMode.dark); + + await SettingsRepository.instance.refresh(); + expect(sut.appConfig.theme.mode, ThemeMode.system); + }); + + test('skips rows whose key is unknown to SettingsKey', () async { + await ctx.db + .into(ctx.db.settingsEntity) + .insert( + SettingsEntityCompanion.insert( + key: 'app-config.unknown.future-key', + value: 'whatever', + updatedAt: Value(DateTime.now()), + ), + ); + + await SettingsRepository.instance.refresh(); + expect(sut.appConfig.theme.mode, ThemeMode.system); + }); + }); + + group('watch', () { + test('watchAppConfig emits the new value after a write', () async { + final expectation = expectLater(sut.watchConfig().map((c) => c.theme.mode), emitsThrough(ThemeMode.dark)); + await sut.write(SettingsKey.themeMode, ThemeMode.dark); + await expectation; + }); + + test('watchConfig emits the new value after a write', () async { + final expectation = expectLater(sut.watchConfig().map((c) => c.logLevel), emitsThrough(LogLevel.warning)); + await sut.write(SettingsKey.logLevel, LogLevel.warning); + await expectation; + }); + }); +} diff --git a/mobile/test/medium/repositories/timeline_repository_test.dart b/mobile/test/medium/repositories/timeline_repository_test.dart new file mode 100644 index 0000000000..f604f53c3e --- /dev/null +++ b/mobile/test/medium/repositories/timeline_repository_test.dart @@ -0,0 +1,73 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; +import 'package:immich_mobile/infrastructure/repositories/timeline.repository.dart'; +import 'package:intl/date_symbol_data_local.dart'; + +import '../repository_context.dart'; + +void main() { + late MediumRepositoryContext ctx; + late DriftTimelineRepository sut; + + setUpAll(() async { + await initializeDateFormatting(); + }); + + setUp(() { + ctx = MediumRepositoryContext(); + sut = DriftTimelineRepository(ctx.db); + }); + + tearDown(() async { + await ctx.dispose(); + }); + + group('remoteAlbum assets', () { + test('no duplicate assets when identical checksum appears in multiple local asset rows', () async { + // Regression check for #23273: a LEFT OUTER JOIN on checksum would fan out and create duplicates + // happens when same photo exists in multiple albums on device + final user = await ctx.newUser(); + final checksum = 'yolo'; + final album = await ctx.newRemoteAlbum(ownerId: user.id); + final remoteAsset = await ctx.newRemoteAsset(ownerId: user.id, checksum: checksum); + await ctx.newRemoteAlbumAsset(albumId: album.id, assetId: remoteAsset.id); + + final localAsset1 = await ctx.newLocalAsset(checksum: checksum); + final localAsset2 = await ctx.newLocalAsset(checksum: checksum); + + final query = sut.remoteAlbum(album.id, .day); + + final buckets = await query.bucketSource().first; + expect(buckets, hasLength(1)); + expect(buckets.single.assetCount, 1); + + final assets = await query.assetSource(0, 10); + expect(assets, hasLength(1)); + expect((assets.first as RemoteAsset).id, remoteAsset.id); + expect([localAsset1.id, localAsset2.id], contains((assets.first as RemoteAsset).localId)); + }); + }); + + group('person assets', () { + test('does not duplicate an asset that has multiple face records for the same person', () async { + // Regression check for #26723: an INNER JOIN between remote_asset_entity and asset_face_entity + // fanned out one asset into N rows when N face records pointed at the same (asset, person) pair + final user = await ctx.newUser(); + final asset = await ctx.newRemoteAsset(ownerId: user.id); + + final person = await ctx.newPerson(ownerId: user.id); + await ctx.newFace(assetId: asset.id, personId: person.id); + await ctx.newFace(assetId: asset.id, personId: person.id); + + final query = sut.person(user.id, person.id, .day); + + final buckets = await query.bucketSource().first; + expect(buckets, hasLength(1)); + expect(buckets.single.assetCount, 1); + + final assets = await query.assetSource(0, 10); + expect(assets, hasLength(1)); + expect((assets.first as RemoteAsset).id, asset.id); + }); + }); +} diff --git a/mobile/test/medium/repository_context.dart b/mobile/test/medium/repository_context.dart index 6e00f268fa..13f9a0234e 100644 --- a/mobile/test/medium/repository_context.dart +++ b/mobile/test/medium/repository_context.dart @@ -1,14 +1,14 @@ -import 'dart:math'; - import 'package:drift/drift.dart'; import 'package:drift/native.dart'; import 'package:immich_mobile/domain/models/album/album.model.dart'; import 'package:immich_mobile/domain/models/album/local_album.model.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/user.model.dart'; +import 'package:immich_mobile/infrastructure/entities/asset_face.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/local_album.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/local_album_asset.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart'; +import 'package:immich_mobile/infrastructure/entities/person.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/remote_album.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/remote_album_asset.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/remote_album_user.entity.drift.dart'; @@ -23,7 +23,6 @@ import '../utils.dart'; class MediumRepositoryContext { final Drift db; - final Random _random = Random(); MediumRepositoryContext() : db = Drift(DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true)); @@ -33,7 +32,7 @@ class MediumRepositoryContext { static Value _resolveUndefined(T? plain, Option? option, T fallback) { if (plain != null) { - return Value(plain); + return .new(plain); } return _resolveOption(option, fallback); @@ -44,7 +43,7 @@ class MediumRepositoryContext { return option.fold(Value.new, Value.absent); } - return Value(fallback); + return .new(fallback); } Future newUser({ @@ -54,17 +53,17 @@ class MediumRepositoryContext { DateTime? profileChangedAt, bool? hasProfileImage, }) async { - id = TestUtils.uuid(id); + id ??= TestUtils.uuid(); return await db .into(db.userEntity) .insertReturning( UserEntityCompanion( - id: Value(id), - email: Value(email ?? '$id@test.com'), - name: Value(email ?? 'user_$id'), - avatarColor: Value(avatarColor ?? AvatarColor.values[_random.nextInt(AvatarColor.values.length)]), - profileChangedAt: Value(TestUtils.date(profileChangedAt)), - hasProfileImage: Value(hasProfileImage ?? false), + id: .new(id), + email: .new(email ?? '$id@test.com'), + name: .new(email ?? 'user_$id'), + avatarColor: .new(avatarColor ?? TestUtils.randElement(AvatarColor.values)), + profileChangedAt: .new(TestUtils.date(profileChangedAt)), + hasProfileImage: .new(hasProfileImage ?? false), ), ); } @@ -88,31 +87,31 @@ class MediumRepositoryContext { String? thumbHash, String? libraryId, }) async { - id = TestUtils.uuid(id); - createdAt = TestUtils.date(createdAt); + id ??= TestUtils.uuid(); + createdAt ??= TestUtils.date(); return db .into(db.remoteAssetEntity) .insertReturning( RemoteAssetEntityCompanion( - id: Value(id), - name: Value('remote_$id.jpg'), - checksum: Value(TestUtils.uuid(checksum)), - type: Value(type ?? AssetType.image), - createdAt: Value(createdAt), - updatedAt: Value(TestUtils.date(updatedAt)), - ownerId: Value(TestUtils.uuid(ownerId)), - visibility: Value(visibility ?? AssetVisibility.timeline), - deletedAt: Value(deletedAt), - durationMs: Value(durationMs ?? 0), - width: Value(width ?? _random.nextInt(1000)), - height: Value(height ?? _random.nextInt(1000)), - isFavorite: Value(isFavorite ?? false), - isEdited: Value(isEdited ?? false), - livePhotoVideoId: Value(livePhotoVideoId), - stackId: Value(stackId), - localDateTime: Value(createdAt.toLocal()), - thumbHash: Value(TestUtils.uuid(thumbHash)), - libraryId: Value(TestUtils.uuid(libraryId)), + id: .new(id), + name: .new('remote_$id.jpg'), + checksum: .new(TestUtils.uuid(checksum)), + type: .new(type ?? .image), + createdAt: .new(createdAt), + updatedAt: .new(TestUtils.date(updatedAt)), + ownerId: .new(TestUtils.uuid(ownerId)), + visibility: .new(visibility ?? .timeline), + deletedAt: .new(deletedAt), + durationMs: .new(durationMs ?? 0), + width: .new(width ?? TestUtils.randInt(1000)), + height: .new(height ?? TestUtils.randInt(1000)), + isFavorite: .new(isFavorite ?? false), + isEdited: .new(isEdited ?? false), + livePhotoVideoId: .new(livePhotoVideoId), + stackId: .new(stackId), + localDateTime: .new(createdAt.toLocal()), + thumbHash: .new(TestUtils.uuid(thumbHash)), + libraryId: .new(TestUtils.uuid(libraryId)), ), ); } @@ -130,12 +129,12 @@ class MediumRepositoryContext { .into(db.remoteAssetCloudIdEntity) .insertReturning( RemoteAssetCloudIdEntityCompanion( - assetId: Value(TestUtils.uuid(id)), - cloudId: Value(TestUtils.uuid(cloudId)), - createdAt: Value(TestUtils.date(createdAt)), + assetId: .new(TestUtils.uuid(id)), + cloudId: .new(TestUtils.uuid(cloudId)), + createdAt: .new(TestUtils.date(createdAt)), adjustmentTime: _resolveUndefined(adjustmentTime, adjustmentTimeOption, DateTime.now()), - latitude: _resolveOption(latitude, _random.nextDouble() * 180 - 90), - longitude: _resolveOption(longitude, _random.nextDouble() * 360 - 180), + latitude: _resolveOption(latitude, TestUtils.randDouble(-90, 90)), + longitude: _resolveOption(longitude, TestUtils.randDouble(-180, 180)), ), ); } @@ -151,40 +150,81 @@ class MediumRepositoryContext { AlbumAssetOrder? order, String? thumbnailAssetId, }) async { - id = TestUtils.uuid(id); - + id ??= TestUtils.uuid(); final album = await db .into(db.remoteAlbumEntity) .insertReturning( RemoteAlbumEntityCompanion( - id: Value(id), - name: Value(name ?? 'remote_album_$id'), - createdAt: Value(TestUtils.date(createdAt)), - updatedAt: Value(TestUtils.date(updatedAt)), - description: Value(description ?? 'Description for album $id'), - isActivityEnabled: Value(isActivityEnabled ?? false), - order: Value(order ?? AlbumAssetOrder.asc), - thumbnailAssetId: Value(thumbnailAssetId), + id: .new(id), + name: .new(name ?? 'remote_album_$id'), + createdAt: .new(TestUtils.date(createdAt)), + updatedAt: .new(TestUtils.date(updatedAt)), + description: .new(description ?? 'Description for album $id'), + isActivityEnabled: .new(isActivityEnabled ?? false), + order: .new(order ?? .asc), + thumbnailAssetId: .new(thumbnailAssetId), ), ); await db .into(db.remoteAlbumUserEntity) .insert( - RemoteAlbumUserEntityCompanion.insert( - albumId: id, - userId: ownerId ?? const Uuid().v4(), - role: AlbumUserRole.owner, + RemoteAlbumUserEntityCompanion( + albumId: .new(id), + userId: .new(TestUtils.uuid(ownerId)), + role: const .new(.owner), ), ); return album; } - Future insertRemoteAlbumAsset({required String albumId, required String assetId}) { + Future newRemoteAlbumAsset({required String albumId, required String assetId}) { return db .into(db.remoteAlbumAssetEntity) - .insert(RemoteAlbumAssetEntityCompanion.insert(albumId: albumId, assetId: assetId)); + .insert(RemoteAlbumAssetEntityCompanion(albumId: .new(albumId), assetId: .new(assetId))); + } + + Future newPerson({String? id, String? ownerId, String? name, bool? isFavorite, bool? isHidden}) { + id ??= TestUtils.uuid(); + return db + .into(db.personEntity) + .insertReturning( + PersonEntityCompanion( + id: .new(id), + ownerId: .new(TestUtils.uuid(ownerId)), + name: .new(name ?? 'person_$id'), + isFavorite: .new(isFavorite ?? false), + isHidden: .new(isHidden ?? false), + ), + ); + } + + Future newFace({String? assetId, String? personId, int? imageWidth, int? imageHeight}) { + imageWidth ??= TestUtils.randInt(999) + 1; + imageHeight ??= TestUtils.randInt(999) + 1; + + final x1 = TestUtils.randInt(imageWidth - 1); + final y1 = TestUtils.randInt(imageHeight - 1); + final x2 = x1 + 1 + TestUtils.randInt(imageWidth - x1 - 1); + final y2 = y1 + 1 + TestUtils.randInt(imageHeight - y1 - 1); + + return db + .into(db.assetFaceEntity) + .insertReturning( + AssetFaceEntityCompanion( + id: .new(TestUtils.uuid()), + assetId: .new(TestUtils.uuid(assetId)), + personId: .new(TestUtils.uuid(personId)), + imageWidth: .new(imageWidth), + imageHeight: .new(imageHeight), + boundingBoxX1: .new(x1), + boundingBoxY1: .new(y1), + boundingBoxX2: .new(x2), + boundingBoxY2: .new(y2), + sourceType: const .new('machine-learning'), + ), + ); } Future newLocalAsset({ @@ -206,26 +246,26 @@ class MediumRepositoryContext { int? orientation, DateTime? updatedAt, }) async { - id = TestUtils.uuid(id); + id ??= TestUtils.uuid(); return db .into(db.localAssetEntity) .insertReturning( LocalAssetEntityCompanion( - id: Value(id), - name: Value(name ?? 'local_$id.jpg'), - height: Value(height ?? _random.nextInt(1000)), - width: Value(width ?? _random.nextInt(1000)), - durationMs: Value(durationMs ?? 0), - orientation: Value(orientation ?? 0), - updatedAt: Value(TestUtils.date(updatedAt)), + id: .new(id), + name: .new(name ?? 'local_$id.jpg'), + height: .new(height ?? TestUtils.randInt(1000)), + width: .new(width ?? TestUtils.randInt(1000)), + durationMs: .new(durationMs ?? 0), + orientation: .new(orientation ?? 0), + updatedAt: .new(TestUtils.date(updatedAt)), checksum: _resolveUndefined(checksum, checksumOption, const Uuid().v4()), - createdAt: Value(TestUtils.date(createdAt)), - type: Value(type ?? AssetType.image), - isFavorite: Value(isFavorite ?? false), - iCloudId: Value(TestUtils.uuid(iCloudId)), + createdAt: .new(TestUtils.date(createdAt)), + type: .new(type ?? .image), + isFavorite: .new(isFavorite ?? false), + iCloudId: .new(TestUtils.uuid(iCloudId)), adjustmentTime: _resolveUndefined(adjustmentTime, adjustmentTimeOption, DateTime.now()), - latitude: Value(latitude ?? _random.nextDouble() * 180 - 90), - longitude: Value(longitude ?? _random.nextDouble() * 360 - 180), + latitude: .new(latitude ?? TestUtils.randDouble(-90, 90)), + longitude: .new(longitude ?? TestUtils.randDouble(-180, 180)), ), ); } @@ -238,24 +278,22 @@ class MediumRepositoryContext { bool? isIosSharedAlbum, String? linkedRemoteAlbumId, }) { - id = TestUtils.uuid(id); + id ??= TestUtils.uuid(); return db .into(db.localAlbumEntity) .insertReturning( LocalAlbumEntityCompanion( - id: Value(id), - name: Value(name ?? 'local_album_$id'), - updatedAt: Value(TestUtils.date(updatedAt)), - backupSelection: Value(backupSelection ?? BackupSelection.none), - isIosSharedAlbum: Value(isIosSharedAlbum ?? false), - linkedRemoteAlbumId: Value(linkedRemoteAlbumId), + id: .new(id), + name: .new(name ?? 'local_album_$id'), + updatedAt: .new(TestUtils.date(updatedAt)), + backupSelection: .new(backupSelection ?? .none), + isIosSharedAlbum: .new(isIosSharedAlbum ?? false), + linkedRemoteAlbumId: .new(linkedRemoteAlbumId), ), ); } - Future newLocalAlbumAsset({required String albumId, required String assetId}) { - return db - .into(db.localAlbumAssetEntity) - .insert(LocalAlbumAssetEntityCompanion.insert(albumId: albumId, assetId: assetId)); - } + Future newLocalAlbumAsset({required String albumId, required String assetId}) => db + .into(db.localAlbumAssetEntity) + .insert(LocalAlbumAssetEntityCompanion(albumId: .new(albumId), assetId: .new(assetId))); } diff --git a/mobile/test/openapi_patches_coverage.dart b/mobile/test/openapi_patches_coverage.dart new file mode 100644 index 0000000000..c4225d82c6 --- /dev/null +++ b/mobile/test/openapi_patches_coverage.dart @@ -0,0 +1,111 @@ +// Intentionally NOT named `*_test.dart`: that suffix makes `flutter test` +// auto-discover it, which would run it on every mobile PR. This check is only +// relevant when the OpenAPI spec changes, so the `Check OpenAPI` workflow runs +// it by explicit path with the spec locations in the environment. + +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:immich_mobile/utils/openapi_patching.dart'; + +void main() { + test('every newly-required response field has a backward-compat patch', () { + final basePath = Platform.environment['OPENAPI_BASE_SPEC']; + final revisionPath = Platform.environment['OPENAPI_REVISION_SPEC']; + if (basePath == null || revisionPath == null) { + markTestSkipped('set OPENAPI_BASE_SPEC and OPENAPI_REVISION_SPEC to run'); + return; + } + + final baseRequired = _requiredBySchema(_loadSpec(basePath)); + final revisionSpec = _loadSpec(revisionPath); + final revisionRequired = _requiredBySchema(revisionSpec); + final deserialized = _deserializedSchemas(revisionSpec); + final patched = openApiPatches.map( + (type, fields) => MapEntry(type, fields.keys.toSet()), + ); + + final missing = []; + for (final entry in revisionRequired.entries) { + if (!deserialized.contains(entry.key)) { + continue; + } + final have = patched[entry.key] ?? const {}; + final newlyRequired = entry.value.difference( + baseRequired[entry.key] ?? const {}, + ); + for (final field in newlyRequired) { + if (!have.contains(field)) { + missing.add('${entry.key}.$field'); + } + } + } + missing.sort(); + + expect( + missing, + isEmpty, + reason: + 'Detected a breaking change: $missing\n' + 'Either add a default to openApiPatches in lib/utils/openapi_patching.dart, or make it optional', + ); + }); +} + +Map _loadSpec(String path) => + jsonDecode(File(path).readAsStringSync()) as Map; + +Map _schemas(Map spec) => + ((spec['components'] as Map?)?['schemas'] as Map?) + ?.cast() ?? + const {}; + +Map> _requiredBySchema(Map spec) { + final result = >{}; + _schemas(spec).forEach((name, schema) { + final required = (schema as Map)['required'] as List? ?? const []; + result[name] = required.cast().toSet(); + }); + return result; +} + +Iterable _refsIn(Object? node) sync* { + if (node is Map) { + if (node[r'$ref'] case final String ref) { + yield ref.split('/').last; + } + for (final value in node.values) { + yield* _refsIn(value); + } + } else if (node is List) { + for (final value in node) { + yield* _refsIn(value); + } + } +} + +Set _deserializedSchemas(Map spec) { + final schemas = _schemas(spec); + final reachable = {}; + + final queue = []; + for (final path in (spec['paths'] as Map?)?.values ?? const []) { + if (path is! Map) { + continue; + } + for (final operation in path.values) { + if (operation is Map) { + queue.addAll(_refsIn(operation['responses'])); + } + } + } + while (queue.isNotEmpty) { + final name = queue.removeLast(); + if (!schemas.containsKey(name) || !reachable.add(name)) { + continue; + } + queue.addAll(_refsIn(schemas[name])); + } + return reachable; +} diff --git a/mobile/test/providers/view_intent/view_intent_handler_android_test.dart b/mobile/test/providers/view_intent/view_intent_handler_android_test.dart new file mode 100644 index 0000000000..f9c2c9d323 --- /dev/null +++ b/mobile/test/providers/view_intent/view_intent_handler_android_test.dart @@ -0,0 +1,261 @@ +import 'dart:async'; + +import 'package:auto_route/auto_route.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; +import 'package:immich_mobile/domain/models/timeline.model.dart'; +import 'package:immich_mobile/domain/services/timeline.service.dart'; +import 'package:immich_mobile/domain/services/user.service.dart'; +import 'package:immich_mobile/models/auth/auth_state.model.dart'; +import 'package:immich_mobile/platform/view_intent_api.g.dart'; +import 'package:immich_mobile/providers/auth.provider.dart'; +import 'package:immich_mobile/providers/view_intent/view_intent_handler_android.dart'; +import 'package:immich_mobile/providers/view_intent/view_intent_pending.provider.dart'; +import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/services/view_intent.service.dart'; +import 'package:immich_mobile/services/view_intent_asset_resolver.service.dart'; +import 'package:immich_mobile/services/auth.service.dart'; +import 'package:immich_mobile/services/api.service.dart'; +import 'package:immich_mobile/services/secure_storage.service.dart'; +import 'package:immich_mobile/services/widget.service.dart'; +import 'package:mocktail/mocktail.dart'; + +class MockViewIntentHostApi extends Mock implements ViewIntentHostApi {} + +class MockViewIntentAssetResolver extends Mock implements ViewIntentAssetResolver {} + +class MockAppRouter extends Mock implements AppRouter {} + +class MockAuthService extends Mock implements AuthService {} + +class MockApiService extends Mock implements ApiService {} + +class MockUserService extends Mock implements UserService {} + +class MockSecureStorageService extends Mock implements SecureStorageService {} + +class MockWidgetService extends Mock implements WidgetService {} + +class FakePageRouteInfo extends Fake implements PageRouteInfo {} + +class FakeTimelineService extends Fake implements TimelineService {} + +class TestViewIntentService extends ViewIntentService { + ViewIntentPayload? consumedAttachment; + int cleanupStaleTempFilesCalls = 0; + int cleanupManagedTempFileCalls = 0; + final List managedTempPaths = []; + + TestViewIntentService() : super(MockViewIntentHostApi()); + + @override + Future consumeViewIntent() async => consumedAttachment; + + @override + Future cleanupStaleTempFiles() async { + cleanupStaleTempFilesCalls++; + } + + @override + Future cleanupManagedTempFile() async { + cleanupManagedTempFileCalls++; + } + + @override + Future setManagedTempFilePath(String path) async { + managedTempPaths.add(path); + } +} + +class TestAuthNotifier extends AuthNotifier { + TestAuthNotifier(Ref ref, AuthState initial) + : super( + MockAuthService(), + MockApiService(), + MockUserService(), + MockSecureStorageService(), + MockWidgetService(), + ref, + ) { + state = initial; + } + + void setAuthenticated(bool isAuthenticated) { + state = state.copyWith(isAuthenticated: isAuthenticated); + } +} + +final _handlerProvider = Provider((ref) => AndroidViewIntentHandler(ref)); + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + late TestViewIntentService viewIntentService; + late MockViewIntentAssetResolver resolver; + late MockAppRouter router; + late TestAuthNotifier authNotifier; + late ProviderContainer container; + late AndroidViewIntentHandler handler; + late ViewIntentPayload payload; + late LocalAsset deepLinkAsset; + late TimelineService deepLinkTimelineService; + + setUpAll(() { + registerFallbackValue(FakePageRouteInfo()); + registerFallbackValue(>[]); + registerFallbackValue(FakeTimelineService()); + registerFallbackValue( + ViewIntentPayload(path: '/tmp/fallback.jpg', mimeType: 'image/jpeg', localAssetId: 'fallback'), + ); + }); + + setUp(() async { + viewIntentService = TestViewIntentService(); + resolver = MockViewIntentAssetResolver(); + router = MockAppRouter(); + payload = ViewIntentPayload(path: '/tmp/incoming.jpg', mimeType: 'image/jpeg', localAssetId: 'local-1'); + deepLinkAsset = _localAsset(id: 'local-1'); + deepLinkTimelineService = await _createReadyTimelineService([deepLinkAsset], TimelineOrigin.deepLink); + + when(() => router.replaceAll(any())).thenAnswer((_) async {}); + + container = ProviderContainer( + overrides: [ + viewIntentServiceProvider.overrideWithValue(viewIntentService), + viewIntentAssetResolverProvider.overrideWithValue(resolver), + appRouterProvider.overrideWithValue(router), + authProvider.overrideWith((ref) { + authNotifier = TestAuthNotifier(ref, _authState(isAuthenticated: true)); + return authNotifier; + }), + ], + ); + + authNotifier = container.read(authProvider.notifier) as TestAuthNotifier; + handler = container.read(_handlerProvider); + + addTearDown(() async { + await deepLinkTimelineService.dispose(); + container.dispose(); + }); + }); + + test('handle defers unauthenticated attachment', () async { + authNotifier.setAuthenticated(false); + + await handler.handle(payload); + + expect(container.read(viewIntentPendingProvider), payload); + verifyNever(() => resolver.resolve(any())); + }); + + testWidgets('flushDeferredViewIntent consumes the pending attachment and routes the viewer', (tester) async { + authNotifier.setAuthenticated(false); + container.read(viewIntentPendingProvider.notifier).defer(payload); + authNotifier.setAuthenticated(true); + + when(() => resolver.resolve(payload)).thenAnswer((_) async { + return ViewIntentResolvedAsset(asset: deepLinkAsset, timelineService: deepLinkTimelineService); + }); + + unawaited(handler.flushDeferredViewIntent()); + await tester.pump(); + await tester.pump(); + await tester.idle(); + + expect(container.read(viewIntentPendingProvider), isNull); + verify(() => resolver.resolve(payload)).called(1); + }); + + test('flushDeferredViewIntent does nothing when there is no pending attachment', () async { + await handler.flushDeferredViewIntent(); + + verifyNever(() => resolver.resolve(any())); + }); + + test('onAppResumed cleans stale temp files when no attachment is present', () async { + viewIntentService.consumedAttachment = null; + + await handler.onAppResumed(); + + expect(viewIntentService.cleanupStaleTempFilesCalls, 1); + verifyNever(() => resolver.resolve(any())); + }); + + test('onAppResumed does not clean stale temp files while pending attachment exists', () async { + viewIntentService.consumedAttachment = null; + container.read(viewIntentPendingProvider.notifier).defer(payload); + + await handler.onAppResumed(); + + expect(viewIntentService.cleanupStaleTempFilesCalls, 0); + verifyNever(() => resolver.resolve(any())); + }); + + testWidgets('onAppResumed handles attachment immediately when authenticated', (tester) async { + viewIntentService.consumedAttachment = payload; + when(() => resolver.resolve(payload)).thenAnswer( + (_) async => ViewIntentResolvedAsset(asset: deepLinkAsset, timelineService: deepLinkTimelineService), + ); + + unawaited(handler.onAppResumed()); + await tester.pump(); + await tester.pump(); + await tester.pump(); + await tester.idle(); + + verify(() => resolver.resolve(payload)).called(1); + // Routes the user to [TabShell, AssetViewer] so back-press lands on the + // main timeline โ€” mirrors the home-screen widget navigation pattern. + final captured = verify(() => router.replaceAll(captureAny())).captured; + expect(captured, hasLength(1)); + final routes = captured.single as List>; + expect(routes, hasLength(2)); + expect(routes[0].routeName, TabShellRoute.name); + expect(routes[1].routeName, AssetViewerRoute.name); + }); +} + +AuthState _authState({required bool isAuthenticated}) { + return AuthState( + deviceId: 'device-1', + userId: 'user-1', + userEmail: 'user@example.com', + isAuthenticated: isAuthenticated, + name: 'User', + isAdmin: false, + profileImagePath: '', + ); +} + +LocalAsset _localAsset({required String id}) { + return LocalAsset( + id: id, + name: '$id.jpg', + checksum: 'checksum-1', + type: AssetType.image, + createdAt: DateTime(2026, 4, 20), + updatedAt: DateTime(2026, 4, 20), + playbackStyle: AssetPlaybackStyle.image, + isEdited: false, + ); +} + +TimelineService _timelineServiceFromAssets(List assets, TimelineOrigin origin) { + return TimelineService(( + assetSource: (index, count) async => assets.skip(index).take(count).toList(), + bucketSource: () => Stream.value([Bucket(assetCount: assets.length)]), + origin: origin, + )); +} + +Future _createReadyTimelineService(List assets, TimelineOrigin origin) async { + final timelineService = _timelineServiceFromAssets(assets, origin); + // Spin a few async ticks so the internal bucket subscription has populated + // the buffer before tests start asserting against totalAssets. + for (var i = 0; i < 20 && timelineService.totalAssets != assets.length; i++) { + await Future.delayed(Duration.zero); + } + return timelineService; +} diff --git a/mobile/test/providers/view_intent/view_intent_pending_provider_test.dart b/mobile/test/providers/view_intent/view_intent_pending_provider_test.dart new file mode 100644 index 0000000000..a3982e8029 --- /dev/null +++ b/mobile/test/providers/view_intent/view_intent_pending_provider_test.dart @@ -0,0 +1,64 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/platform/view_intent_api.g.dart'; +import 'package:immich_mobile/providers/view_intent/view_intent_pending.provider.dart'; + +void main() { + late DateTime now; + late ProviderContainer container; + + final attachment = ViewIntentPayload( + path: '/tmp/file.jpg', + mimeType: 'image/jpeg', + localAssetId: '42', + ); + + setUp(() { + now = DateTime(2026, 4, 17, 12); + container = ProviderContainer( + overrides: [viewIntentNowProvider.overrideWithValue(() => now)], + ); + addTearDown(container.dispose); + }); + + test('defer stores pending attachment', () { + container.read(viewIntentPendingProvider.notifier).defer(attachment); + + expect(container.read(viewIntentPendingProvider), attachment); + }); + + test('takeIfFresh returns pending attachment once', () { + container.read(viewIntentPendingProvider.notifier).defer(attachment); + + final first = container.read(viewIntentPendingProvider.notifier).takeIfFresh(); + final second = container.read(viewIntentPendingProvider.notifier).takeIfFresh(); + + expect(first, attachment); + expect(second, isNull); + }); + + test('takeIfFresh drops expired attachment', () { + container.read(viewIntentPendingProvider.notifier).defer(attachment); + now = now.add(const Duration(minutes: 11)); + + final result = container.read(viewIntentPendingProvider.notifier).takeIfFresh(); + + expect(result, isNull); + expect(container.read(viewIntentPendingProvider), isNull); + }); + + test('newer deferred attachment replaces older one', () { + final newerAttachment = ViewIntentPayload( + path: '/tmp/file-2.jpg', + mimeType: 'image/jpeg', + localAssetId: '43', + ); + + container.read(viewIntentPendingProvider.notifier).defer(attachment); + container.read(viewIntentPendingProvider.notifier).defer(newerAttachment); + + final result = container.read(viewIntentPendingProvider.notifier).takeIfFresh(); + + expect(result, newerAttachment); + }); +} diff --git a/mobile/test/repository.mocks.dart b/mobile/test/repository.mocks.dart index d049626f1d..80786420fd 100644 --- a/mobile/test/repository.mocks.dart +++ b/mobile/test/repository.mocks.dart @@ -2,15 +2,18 @@ import 'package:immich_mobile/repositories/asset_api.repository.dart'; import 'package:immich_mobile/repositories/asset_media.repository.dart'; import 'package:immich_mobile/repositories/auth.repository.dart'; import 'package:immich_mobile/repositories/auth_api.repository.dart'; -import 'package:immich_mobile/repositories/local_files_manager.repository.dart'; +import 'package:immich_mobile/domain/services/tag.service.dart'; +import 'package:immich_mobile/repositories/permission.repository.dart'; import 'package:mocktail/mocktail.dart'; class MockAssetApiRepository extends Mock implements AssetApiRepository {} class MockAssetMediaRepository extends Mock implements AssetMediaRepository {} +class MockPermissionRepository extends Mock implements IPermissionRepository {} + class MockAuthApiRepository extends Mock implements AuthApiRepository {} class MockAuthRepository extends Mock implements AuthRepository {} -class MockLocalFilesManagerRepository extends Mock implements LocalFilesManagerRepository {} +class MockTagService extends Mock implements TagService {} diff --git a/mobile/test/services/action.service_test.dart b/mobile/test/services/action.service_test.dart index 87263c9ae7..f08a247a3e 100644 --- a/mobile/test/services/action.service_test.dart +++ b/mobile/test/services/action.service_test.dart @@ -27,6 +27,7 @@ void main() { late MockTrashedLocalAssetRepository trashedLocalAssetRepository; late MockAssetMediaRepository assetMediaRepository; late MockDownloadRepository downloadRepository; + late MockTagService tagService; late Drift db; @@ -53,6 +54,7 @@ void main() { trashedLocalAssetRepository = MockTrashedLocalAssetRepository(); assetMediaRepository = MockAssetMediaRepository(); downloadRepository = MockDownloadRepository(); + tagService = MockTagService(); sut = ActionService( assetApiRepository, @@ -63,6 +65,7 @@ void main() { trashedLocalAssetRepository, assetMediaRepository, downloadRepository, + tagService, ); }); diff --git a/mobile/test/services/auth.service_test.dart b/mobile/test/services/auth.service_test.dart index f9a6d5e282..584ea57027 100644 --- a/mobile/test/services/auth.service_test.dart +++ b/mobile/test/services/auth.service_test.dart @@ -6,7 +6,6 @@ import 'package:immich_mobile/domain/services/store.service.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/store.repository.dart'; import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart'; -import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/services/auth.service.dart'; import 'package:mocktail/mocktail.dart'; import 'package:openapi/api.dart'; @@ -22,7 +21,6 @@ void main() { late MockApiService apiService; late MockNetworkService networkService; late MockBackgroundSyncManager backgroundSyncManager; - late MockAppSettingService appSettingsService; late Drift db; setUp(() async { @@ -31,15 +29,12 @@ void main() { apiService = MockApiService(); networkService = MockNetworkService(); backgroundSyncManager = MockBackgroundSyncManager(); - appSettingsService = MockAppSettingService(); - sut = AuthService( authApiRepository, authRepository, apiService, networkService, backgroundSyncManager, - appSettingsService, ); registerFallbackValue(Uri()); @@ -109,36 +104,6 @@ void main() { }); }); - group('logout', () { - test('Should logout user', () async { - when(() => authApiRepository.logout()).thenAnswer((_) async => {}); - when(() => backgroundSyncManager.cancel()).thenAnswer((_) async => {}); - when(() => authRepository.clearLocalData()).thenAnswer((_) => Future.value(null)); - when( - () => appSettingsService.setSetting(AppSettingsEnum.enableBackup, false), - ).thenAnswer((_) => Future.value(null)); - await sut.logout(); - - verify(() => authApiRepository.logout()).called(1); - verify(() => backgroundSyncManager.cancel()).called(1); - verify(() => authRepository.clearLocalData()).called(1); - }); - - test('Should clear local data even on server error', () async { - when(() => authApiRepository.logout()).thenThrow(Exception('Server error')); - when(() => backgroundSyncManager.cancel()).thenAnswer((_) async => {}); - when(() => authRepository.clearLocalData()).thenAnswer((_) => Future.value(null)); - when( - () => appSettingsService.setSetting(AppSettingsEnum.enableBackup, false), - ).thenAnswer((_) => Future.value(null)); - await sut.logout(); - - verify(() => authApiRepository.logout()).called(1); - verify(() => backgroundSyncManager.cancel()).called(1); - verify(() => authRepository.clearLocalData()).called(1); - }); - }); - group('setOpenApiServiceEndpoint', () { setUp(() { when(() => networkService.getWifiName()).thenAnswer((_) async => 'TestWifi'); diff --git a/mobile/test/services/background_upload.service_test.dart b/mobile/test/services/background_upload.service_test.dart index 585ffcb499..310f2f4d49 100644 --- a/mobile/test/services/background_upload.service_test.dart +++ b/mobile/test/services/background_upload.service_test.dart @@ -11,12 +11,11 @@ import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/services/store.service.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/store.repository.dart'; -import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/services/background_upload.service.dart'; import 'package:mocktail/mocktail.dart'; -import '../domain/service.mock.dart'; import '../fixtures/asset.stub.dart'; import '../infrastructure/repository.mock.dart'; import '../mocks/asset_entity.mock.dart'; @@ -28,13 +27,10 @@ void main() { late MockStorageRepository mockStorageRepository; late MockDriftLocalAssetRepository mockLocalAssetRepository; late MockDriftBackupRepository mockBackupRepository; - late MockAppSettingsService mockAppSettingsService; late MockAssetMediaRepository mockAssetMediaRepository; late Drift db; setUpAll(() async { - registerFallbackValue(AppSettingsEnum.useCellularForUploadPhotos); - TestWidgetsFlutterBinding.ensureInitialized(); TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler( const MethodChannel('plugins.flutter.io/path_provider'), @@ -42,6 +38,7 @@ void main() { ); db = Drift(DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true)); await StoreService.init(storeRepository: DriftStoreRepository(db)); + await SettingsRepository.ensureInitialized(db); await Store.put(StoreKey.serverEndpoint, 'http://test-server.com'); await Store.put(StoreKey.deviceId, 'test-device-id'); @@ -52,18 +49,13 @@ void main() { mockStorageRepository = MockStorageRepository(); mockLocalAssetRepository = MockDriftLocalAssetRepository(); mockBackupRepository = MockDriftBackupRepository(); - mockAppSettingsService = MockAppSettingsService(); mockAssetMediaRepository = MockAssetMediaRepository(); - when(() => mockAppSettingsService.getSetting(AppSettingsEnum.useCellularForUploadVideos)).thenReturn(false); - when(() => mockAppSettingsService.getSetting(AppSettingsEnum.useCellularForUploadPhotos)).thenReturn(false); - sut = BackgroundUploadService( mockUploadRepository, mockStorageRepository, mockLocalAssetRepository, mockBackupRepository, - mockAppSettingsService, mockAssetMediaRepository, ); @@ -179,7 +171,6 @@ void main() { mockStorageRepository, mockLocalAssetRepository, mockBackupRepository, - mockAppSettingsService, mockAssetMediaRepository, ); addTearDown(() => sutWithV24.dispose()); @@ -230,7 +221,6 @@ void main() { mockStorageRepository, mockLocalAssetRepository, mockBackupRepository, - mockAppSettingsService, mockAssetMediaRepository, ); addTearDown(() => sutAndroid.dispose()); @@ -271,7 +261,6 @@ void main() { mockStorageRepository, mockLocalAssetRepository, mockBackupRepository, - mockAppSettingsService, mockAssetMediaRepository, ); addTearDown(() => sutWithV24.dispose()); @@ -312,7 +301,6 @@ void main() { mockStorageRepository, mockLocalAssetRepository, mockBackupRepository, - mockAppSettingsService, mockAssetMediaRepository, ); addTearDown(() => sutWithV24.dispose()); diff --git a/mobile/test/services/view_intent_asset_resolver_test.dart b/mobile/test/services/view_intent_asset_resolver_test.dart new file mode 100644 index 0000000000..38d2f71f88 --- /dev/null +++ b/mobile/test/services/view_intent_asset_resolver_test.dart @@ -0,0 +1,123 @@ +import 'dart:async'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; +import 'package:immich_mobile/domain/models/timeline.model.dart'; +import 'package:immich_mobile/domain/services/timeline.service.dart'; +import 'package:immich_mobile/platform/view_intent_api.g.dart'; +import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; +import 'package:immich_mobile/services/view_intent_asset_resolver.service.dart'; +import 'package:mocktail/mocktail.dart'; + +import '../infrastructure/repository.mock.dart'; + +class MockTimelineFactory extends Mock implements TimelineFactory {} + +void main() { + late MockDriftLocalAssetRepository mockLocalAssetRepository; + late MockTimelineFactory timelineFactory; + late List createdTimelineServices; + late ProviderContainer container; + + setUp(() { + mockLocalAssetRepository = MockDriftLocalAssetRepository(); + timelineFactory = MockTimelineFactory(); + createdTimelineServices = []; + + when(() => timelineFactory.fromAssets(any(), TimelineOrigin.deepLink)).thenAnswer((invocation) { + final assets = List.from(invocation.positionalArguments[0] as List); + final timelineService = _timelineServiceFromAssets(assets, TimelineOrigin.deepLink); + createdTimelineServices.add(timelineService); + return timelineService; + }); + + container = ProviderContainer( + overrides: [ + localAssetRepository.overrideWith((ref) => mockLocalAssetRepository), + timelineFactoryProvider.overrideWith((ref) => timelineFactory), + ], + ); + + addTearDown(() async { + for (final timelineService in createdTimelineServices) { + await timelineService.dispose(); + } + container.dispose(); + }); + }); + + test('returns DB-backed local asset wrapped in a 1-element deep-link timeline', () async { + final localAsset = _localAsset(id: 'local-1', checksum: 'checksum-1'); + when(() => mockLocalAssetRepository.getById('local-1')).thenAnswer((_) async => localAsset); + + final result = await _resolve(container, _payload(localAssetId: 'local-1')); + + expect(result.asset, equals(localAsset)); + expect(result.timelineService.origin, TimelineOrigin.deepLink); + expect(result.viewIntentFilePath, isNull, reason: 'DB-backed assets carry their own source โ€” no temp file needed'); + }); + + test('returns transient asset with temp file path when localAssetId has no DB row', () async { + when(() => mockLocalAssetRepository.getById('local-1')).thenAnswer((_) async => null); + + final result = await _resolve(container, _payload(localAssetId: 'local-1', path: '/tmp/incoming.jpg')); + + expect(result.asset, isA()); + expect(result.timelineService.origin, TimelineOrigin.deepLink); + expect(result.viewIntentFilePath, '/tmp/incoming.jpg'); + }); + + test('returns transient asset for path-only attachment', () async { + final result = await _resolve( + container, + _payload(localAssetId: null, path: '/tmp/incoming.webp', mimeType: 'image/webp'), + ); + + expect(result.asset, isA()); + expect(result.timelineService.origin, TimelineOrigin.deepLink); + expect(result.viewIntentFilePath, '/tmp/incoming.webp'); + + final asset = result.asset as LocalAsset; + expect(asset.localId, startsWith('-')); + expect(asset.name, 'incoming.webp'); + expect(asset.playbackStyle, AssetPlaybackStyle.imageAnimated); + }); + + test('throws when neither localAssetId nor path is provided', () async { + await expectLater( + _resolve(container, _payload(localAssetId: null, path: null)), + throwsA(isA()), + ); + }); +} + +Future _resolve(ProviderContainer container, ViewIntentPayload payload) { + return container.read(viewIntentAssetResolverProvider).resolve(payload); +} + +ViewIntentPayload _payload({String? localAssetId = 'local-1', String? path, String mimeType = 'image/jpeg'}) { + return ViewIntentPayload(path: path, mimeType: mimeType, localAssetId: localAssetId); +} + +LocalAsset _localAsset({required String id, String? checksum}) { + return LocalAsset( + id: id, + name: '$id.jpg', + checksum: checksum, + type: AssetType.image, + createdAt: DateTime(2026, 4, 20), + updatedAt: DateTime(2026, 4, 20), + playbackStyle: AssetPlaybackStyle.image, + isEdited: false, + ); +} + +TimelineService _timelineServiceFromAssets(List assets, TimelineOrigin origin) { + return TimelineService(( + assetSource: (index, count) async => assets.skip(index).take(count).toList(), + bucketSource: () => Stream.value([Bucket(assetCount: assets.length)]), + origin: origin, + )); +} diff --git a/mobile/test/services/view_intent_service_test.dart b/mobile/test/services/view_intent_service_test.dart new file mode 100644 index 0000000000..7b3d0b85e7 --- /dev/null +++ b/mobile/test/services/view_intent_service_test.dart @@ -0,0 +1,119 @@ +import 'dart:io'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:immich_mobile/platform/view_intent_api.g.dart'; +import 'package:immich_mobile/services/view_intent.service.dart'; +import 'package:mocktail/mocktail.dart'; + +class MockViewIntentHostApi extends Mock implements ViewIntentHostApi {} + +void main() { + late MockViewIntentHostApi hostApi; + late ViewIntentService service; + late Directory tempRoot; + late Directory cacheDir; + + final attachment = ViewIntentPayload( + path: '/tmp/file.jpg', + mimeType: 'image/jpeg', + localAssetId: '42', + ); + + setUp(() { + hostApi = MockViewIntentHostApi(); + tempRoot = Directory.systemTemp.createTempSync('view-intent-root'); + cacheDir = Directory('${tempRoot.path}/cache')..createSync(); + service = ViewIntentService(hostApi, temporaryDirectory: () async => cacheDir); + }); + + tearDown(() async { + clearInteractions(hostApi); + if (await tempRoot.exists()) { + await tempRoot.delete(recursive: true); + } + }); + + test('consumeViewIntent returns null when no attachment', () async { + when(() => hostApi.consumeViewIntent()).thenAnswer((_) async => null); + + final result = await service.consumeViewIntent(); + + expect(result, isNull); + verify(() => hostApi.consumeViewIntent()).called(1); + }); + + test('consumeViewIntent returns attachment when present', () async { + when(() => hostApi.consumeViewIntent()).thenAnswer((_) async => attachment); + + final result = await service.consumeViewIntent(); + + expect(result, attachment); + verify(() => hostApi.consumeViewIntent()).called(1); + }); + + test('consumeViewIntent swallows host api errors', () async { + when(() => hostApi.consumeViewIntent()).thenThrow(Exception('boom')); + + final result = await service.consumeViewIntent(); + + expect(result, isNull); + verify(() => hostApi.consumeViewIntent()).called(1); + }); + + test('setManagedTempFilePath cleans previous managed temp file', () async { + final firstFile = File('${cacheDir.path}/view_intent_first.jpg')..writeAsStringSync('first'); + final secondFile = File('${cacheDir.path}/view_intent_second.jpg')..writeAsStringSync('second'); + + await service.setManagedTempFilePath(firstFile.path); + await service.setManagedTempFilePath(secondFile.path); + + expect(await firstFile.exists(), isFalse); + expect(await secondFile.exists(), isTrue); + + await service.cleanupManagedTempFile(); + expect(await secondFile.exists(), isFalse); + }); + + test('cleanupTempFile defers deletion while an upload is active', () async { + final tempFile = File('${cacheDir.path}/view_intent_in_flight.jpg')..writeAsStringSync('bytes'); + + service.markUploadActive(tempFile.path); + await service.cleanupTempFile(tempFile.path); + + expect(await tempFile.exists(), isTrue, reason: 'active uploads block cleanup'); + + await service.markUploadInactive(tempFile.path); + expect(await tempFile.exists(), isFalse); + }); + + test('cleanupTempFile ignores non-managed paths', () async { + final nonManagedFile = File('${tempRoot.path}/plain_file.jpg')..writeAsStringSync('content'); + + await service.cleanupTempFile(nonManagedFile.path); + + expect(await nonManagedFile.exists(), isTrue); + }); + + test('cleanupStaleTempFiles removes view-intent temp files and keeps unrelated files', () async { + final firstFile = File('${cacheDir.path}/view_intent_first.jpg')..writeAsStringSync('first'); + final secondFile = File('${cacheDir.path}/view_intent_second.jpg')..writeAsStringSync('second'); + final unrelatedFile = File('${cacheDir.path}/plain_file.jpg')..writeAsStringSync('plain'); + + await service.cleanupStaleTempFiles(); + + expect(await firstFile.exists(), isFalse); + expect(await secondFile.exists(), isFalse); + expect(await unrelatedFile.exists(), isTrue); + }); + + test('cleanupStaleTempFiles skips paths with active uploads', () async { + final stale = File('${cacheDir.path}/view_intent_stale.jpg')..writeAsStringSync('stale'); + final active = File('${cacheDir.path}/view_intent_active.jpg')..writeAsStringSync('active'); + service.markUploadActive(active.path); + + await service.cleanupStaleTempFiles(); + + expect(await stale.exists(), isFalse); + expect(await active.exists(), isTrue); + }); +} diff --git a/mobile/test/test_utils.dart b/mobile/test/test_utils.dart index f29b29050a..17faed4a27 100644 --- a/mobile/test/test_utils.dart +++ b/mobile/test/test_utils.dart @@ -58,6 +58,7 @@ abstract final class TestUtils { type: domain.AssetType.image, createdAt: DateTime(2024, 1, 1), updatedAt: DateTime(2024, 1, 1), + uploadedAt: DateTime(2024, 1, 1), durationMs: 0, isFavorite: false, width: width, diff --git a/mobile/test/unit/repositories/settings_repository_test.dart b/mobile/test/unit/repositories/settings_repository_test.dart new file mode 100644 index 0000000000..80214dd298 --- /dev/null +++ b/mobile/test/unit/repositories/settings_repository_test.dart @@ -0,0 +1,16 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:immich_mobile/domain/models/config/app_config.dart'; +import 'package:immich_mobile/domain/models/settings_key.dart'; + +void main() { + group('SettingsKey', () { + for (final key in SettingsKey.values) { + test('verify codec for $key', () { + final defaultValue = defaultConfig.read(key); + final encoded = key.encode(defaultValue); + final decoded = key.decode(encoded); + expect(decoded, defaultValue, reason: 'round-trip failed for ${key.name}'); + }); + } + }); +} diff --git a/mobile/test/unit/utils/isolate_worker_test.dart b/mobile/test/unit/utils/isolate_worker_test.dart new file mode 100644 index 0000000000..5b79395f52 --- /dev/null +++ b/mobile/test/unit/utils/isolate_worker_test.dart @@ -0,0 +1,23 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:immich_mobile/wm_executor.dart'; + +void main() { + tearDown(workerManagerPatch.dispose); + + test('dispose() drains a cancelled task and delivers its result', () async { + await workerManagerPatch.init(isolatesCount: 1, dynamicSpawning: false); + + final task = workerManagerPatch.executeGentle((onCancel) async { + await onCancel.future; + return 'drained'; + }); + + await workerManagerPatch.dispose(); + + expect( + await task.timeout(const Duration(seconds: 5)), + 'drained', + reason: 'the worker must finish and return its result, not be killed mid-task', + ); + }); +} diff --git a/mobile/test/unit/utils/semver_test.dart b/mobile/test/unit/utils/semver_test.dart index 8f1958a879..1e534af593 100644 --- a/mobile/test/unit/utils/semver_test.dart +++ b/mobile/test/unit/utils/semver_test.dart @@ -88,5 +88,71 @@ void main() { expect(version2.minor, 2); expect(version2.patch, 3); }); + + test('Orders later prerelease above earlier prerelease', () { + const rc1 = SemVer(major: 1, minor: 151, patch: 0, prerelease: 1); + const rc2 = SemVer(major: 1, minor: 151, patch: 0, prerelease: 2); + expect(rc2 > rc1, isTrue); + expect(rc1 < rc2, isTrue); + expect(rc1 == rc2, isFalse); + }); + + test('Final release outranks its prerelease of the same version', () { + const rc = SemVer(major: 1, minor: 151, patch: 0, prerelease: 1); + const release = SemVer(major: 1, minor: 151, patch: 0); + expect(release > rc, isTrue); + expect(rc < release, isTrue); + }); + + test('Higher major outranks a prerelease regardless of ordinal', () { + const rc = SemVer(major: 1, minor: 151, patch: 0, prerelease: 9); + const next = SemVer(major: 2, minor: 0, patch: 0); + expect(next > rc, isTrue); + }); + + test('Equal prerelease versions compare as equal', () { + const a = SemVer(major: 1, minor: 151, patch: 0, prerelease: 3); + const b = SemVer(major: 1, minor: 151, patch: 0, prerelease: 3); + expect(a == b, isTrue); + expect(a > b, isFalse); + expect(a < b, isFalse); + }); + + test('Reports prerelease difference type', () { + const rc1 = SemVer(major: 1, minor: 151, patch: 0, prerelease: 1); + const rc2 = SemVer(major: 1, minor: 151, patch: 0, prerelease: 2); + expect(rc1.differenceType(rc2), SemVerType.prerelease); + }); + + test('toString includes prerelease suffix when present', () { + const rc = SemVer(major: 1, minor: 151, patch: 0, prerelease: 2); + expect(rc.toString(), '1.151.0-rc.2'); + }); + + test('Parses prerelease ordinal from -rc strings', () { + final dotted = SemVer.fromString('1.151.0-rc.2'); + expect(dotted.major, 1); + expect(dotted.minor, 151); + expect(dotted.patch, 0); + expect(dotted.prerelease, 2); + + expect(SemVer.fromString('v1.151.0-rc.3').prerelease, 3); + expect(SemVer.fromString('1.2.3-rc.2+build.5').prerelease, 2); + }); + + test('Plain version string has null prerelease', () { + expect(SemVer.fromString('3.0.0').prerelease, isNull); + }); + + test('Invalid rc suffixes parse without error and have null prerelease', () { + final debug = SemVer.fromString('1.2.3-debug'); + expect(debug.major, 1); + expect(debug.minor, 2); + expect(debug.patch, 3); + expect(debug.prerelease, isNull); + + expect(SemVer.fromString('1.2.3+build.5').prerelease, isNull); + expect(SemVer.fromString('1.151.0-rc4').prerelease, isNull); + }); }); } diff --git a/mobile/test/utils.dart b/mobile/test/utils.dart index 7967083efc..aa1f82dc2b 100644 --- a/mobile/test/utils.dart +++ b/mobile/test/utils.dart @@ -1,10 +1,22 @@ +import 'dart:math'; + import 'package:uuid/uuid.dart'; class TestUtils { + static final _random = Random(); + static String uuid([String? id]) => id ?? const Uuid().v4(); static DateTime date([DateTime? date]) => date ?? DateTime.now(); static DateTime now() => DateTime.now(); static DateTime yesterday() => DateTime.now().subtract(const Duration(days: 1)); static DateTime tomorrow() => DateTime.now().add(const Duration(days: 1)); + + static T randElement(List list) => list[_random.nextInt(list.length)]; + static int randInt([int? max]) => max != null ? _random.nextInt(max) : _random.nextInt(1 << 32); + static double randDouble([int? max, int? min]) { + final minValue = min ?? 0; + final maxValue = max ?? 1; + return minValue + _random.nextDouble() * (maxValue - minValue); + } } diff --git a/mobile/test/utils_legacy/action_button_utils_test.dart b/mobile/test/utils_legacy/action_button_utils_test.dart index 9956dfa2d0..0a6020762a 100644 --- a/mobile/test/utils_legacy/action_button_utils_test.dart +++ b/mobile/test/utils_legacy/action_button_utils_test.dart @@ -3,6 +3,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/domain/models/album/album.model.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; +import 'package:immich_mobile/domain/services/timeline.service.dart'; import 'package:immich_mobile/utils/action_button.utils.dart'; LocalAsset createLocalAsset({ @@ -35,7 +36,9 @@ RemoteAsset createRemoteAsset({ AssetType type = AssetType.image, DateTime? createdAt, DateTime? updatedAt, + DateTime? uploadedAt, bool isFavorite = false, + DateTime? deletedAt, }) { return RemoteAsset( id: 'remote-id', @@ -46,8 +49,10 @@ RemoteAsset createRemoteAsset({ ownerId: 'owner-id', createdAt: createdAt ?? DateTime.now(), updatedAt: updatedAt ?? DateTime.now(), + uploadedAt: uploadedAt ?? DateTime.now(), isFavorite: isFavorite, isEdited: false, + deletedAt: deletedAt, ); } @@ -456,6 +461,62 @@ void main() { expect(ActionButtonType.trash.shouldShow(context), isFalse); }); + + test('should not show when asset is already trashed', () { + final remoteAsset = createRemoteAsset(deletedAt: DateTime(2024)); + final context = ActionButtonContext( + asset: remoteAsset, + isOwner: true, + isArchived: false, + isTrashEnabled: true, + isInLockedView: false, + currentAlbum: null, + advancedTroubleshooting: false, + isStacked: false, + source: ActionSource.viewer, + timelineOrigin: TimelineOrigin.trash, + ); + + expect(ActionButtonType.trash.shouldShow(context), isFalse); + }); + }); + + group('restoreTrash button', () { + test('should show when owner, not locked, has remote, and is in trash timeline', () { + final remoteAsset = createRemoteAsset(); + final context = ActionButtonContext( + asset: remoteAsset, + isOwner: true, + isArchived: false, + isTrashEnabled: true, + isInLockedView: false, + currentAlbum: null, + advancedTroubleshooting: false, + isStacked: false, + source: ActionSource.timeline, + timelineOrigin: TimelineOrigin.trash, + ); + + expect(ActionButtonType.restoreTrash.shouldShow(context), isTrue); + }); + + test('should not show when not in trash timeline', () { + final remoteAsset = createRemoteAsset(); + final context = ActionButtonContext( + asset: remoteAsset, + isOwner: true, + isArchived: false, + isTrashEnabled: false, + isInLockedView: false, + currentAlbum: null, + advancedTroubleshooting: false, + isStacked: false, + source: ActionSource.timeline, + timelineOrigin: TimelineOrigin.main, + ); + + expect(ActionButtonType.restoreTrash.shouldShow(context), isFalse); + }); }); group('deletePermanent button', () { @@ -492,6 +553,24 @@ void main() { expect(ActionButtonType.deletePermanent.shouldShow(context), isFalse); }); + + test('should show when asset is trashed even with trash enabled', () { + final remoteAsset = createRemoteAsset(deletedAt: DateTime(2024)); + final context = ActionButtonContext( + asset: remoteAsset, + isOwner: true, + isArchived: false, + isTrashEnabled: true, + isInLockedView: false, + currentAlbum: null, + advancedTroubleshooting: false, + isStacked: false, + source: ActionSource.viewer, + timelineOrigin: TimelineOrigin.trash, + ); + + expect(ActionButtonType.deletePermanent.shouldShow(context), isTrue); + }); }); group('delete button', () { diff --git a/open-api/bin/generate-dart-sdk.sh b/open-api/bin/generate-dart-sdk.sh new file mode 100755 index 0000000000..793c1f8df3 --- /dev/null +++ b/open-api/bin/generate-dart-sdk.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +OPENAPI_GENERATOR_VERSION=v7.22.0 + +set -euo pipefail + +# usage: ./bin/generate-dart-sdk.sh + +rm -rf ../mobile/openapi + +cd ./templates/mobile/serialization/native +wget -O native_class.mustache https://raw.githubusercontent.com/OpenAPITools/openapi-generator/$OPENAPI_GENERATOR_VERSION/modules/openapi-generator/src/main/resources/dart2/serialization/native/native_class.mustache +patch --no-backup-if-mismatch -u native_class.mustache headerParams, + Map formParams, +- String? contentType, +- ) async { ++ String? contentType, { ++ Future? abortTrigger, ++ }) async { + await authentication?.applyToParams(queryParams, headerParams); + + headerParams.addAll(_defaultHeaderMap); +@@ -63,7 +64,7 @@ + body is MultipartFile && (contentType == null || + !contentType.toLowerCase().startsWith('multipart/form-data')) + ) { +- final request = StreamedRequest(method, uri); ++ final request = AbortableStreamedRequest(method, uri, abortTrigger: abortTrigger); + request.headers.addAll(headerParams); + request.contentLength = body.length; + body.finalize().listen( +@@ -78,7 +79,7 @@ + } + + if (body is MultipartRequest) { +- final request = MultipartRequest(method, uri); ++ final request = AbortableMultipartRequest(method, uri, abortTrigger: abortTrigger); + request.fields.addAll(body.fields); + request.files.addAll(body.files); + request.headers.addAll(body.headers); +@@ -92,14 +93,19 @@ + : await serializeAsync(body); + final nullableHeaderParams = headerParams.isEmpty ? null : headerParams; + +- switch(method) { +- case 'POST': return await _client.post(uri, headers: nullableHeaderParams, body: msgBody,); +- case 'PUT': return await _client.put(uri, headers: nullableHeaderParams, body: msgBody,); +- case 'DELETE': return await _client.delete(uri, headers: nullableHeaderParams, body: msgBody,); +- case 'PATCH': return await _client.patch(uri, headers: nullableHeaderParams, body: msgBody,); +- case 'HEAD': return await _client.head(uri, headers: nullableHeaderParams,); +- case 'GET': return await _client.get(uri, headers: nullableHeaderParams,); ++ final request = AbortableRequest(method, uri, abortTrigger: abortTrigger); ++ if (nullableHeaderParams != null) { ++ request.headers.addAll(nullableHeaderParams); + } ++ if (msgBody is String) { ++ request.body = msgBody; ++ } else if (msgBody is List) { ++ request.bodyBytes = msgBody; ++ } else if (msgBody is Map) { ++ request.bodyFields = msgBody; ++ } ++ final response = await _client.send(request); ++ return Response.fromStream(response); + } on SocketException catch (error, trace) { + throw ApiException.withInner( + HttpStatus.badRequest, +@@ -136,26 +146,21 @@ + trace, + ); + } +- +- throw ApiException( +- HttpStatus.badRequest, +- 'Invalid HTTP operation: $method $path', +- ); } - + - Future deserializeAsync(String value, String targetType, {bool growable = false,}) async => + Future deserializeAsync(String value, String targetType, {bool growable = false,}) => // ignore: deprecated_member_use_from_same_package deserialize(value, targetType, growable: growable); - + @Deprecated('Scheduled for removal in OpenAPI Generator 6.x. Use deserializeAsync() instead.') - dynamic deserialize(String value, String targetType, {bool growable = false,}) { + Future deserialize(String value, String targetType, {bool growable = false,}) async { // Remove all spaces. Necessary for regular expressions as well. targetType = targetType.replaceAll(' ', ''); // ignore: parameter_assignments - + // If the expected target type is String, nothing to do... return targetType == 'String' ? value - : fromJson(json.decode(value), targetType, growable: growable); + : fromJson(await compute((String j) => json.decode(j), value), targetType, growable: growable); } + + // ignore: deprecated_member_use_from_same_package diff --git a/open-api/patch/time_bucket_asset_response_dto.dart.patch b/open-api/patch/time_bucket_asset_response_dto.dart.patch new file mode 100644 index 0000000000..0ff420c4eb --- /dev/null +++ b/open-api/patch/time_bucket_asset_response_dto.dart.patch @@ -0,0 +1,9 @@ +@@ -83,7 +83,7 @@ + List ratio; + + /// Array of stack information as [stackId, assetCount] tuples (null for non-stacked assets) +- Optional>?> stack; ++ Optional?>?> stack; + + /// Array of BlurHash strings for generating asset previews (base64 encoded) + List thumbhash; diff --git a/open-api/templates/mobile/api.mustache b/open-api/templates/mobile/api.mustache index ac32571123..8c6d2bb96e 100644 --- a/open-api/templates/mobile/api.mustache +++ b/open-api/templates/mobile/api.mustache @@ -1,5 +1,6 @@ {{>header}} {{>part_of}} + {{#operations}} class {{{classname}}} { @@ -49,7 +50,7 @@ class {{{classname}}} { /// {{/-last}} {{/allParams}} - Future {{{nickname}}}WithHttpInfo({{#allParams}}{{#required}}{{{dataType}}} {{{paramName}}},{{^-last}} {{/-last}}{{/required}}{{/allParams}}{{#hasOptionalParams}}{ {{#allParams}}{{^required}}{{{dataType}}}? {{{paramName}}},{{^-last}} {{/-last}}{{/required}}{{/allParams}} }{{/hasOptionalParams}}) async { + Future {{{nickname}}}WithHttpInfo({{#allParams}}{{#required}}{{{dataType}}} {{{paramName}}}, {{/required}}{{/allParams}}{ {{#allParams}}{{^required}}{{{dataType}}}? {{{paramName}}}, {{/required}}{{/allParams}}Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'{{{path}}}'{{#pathParams}} .replaceAll({{=<% %>=}}'{<% baseName %>}'<%={{ }}=%>, {{{paramName}}}{{^isString}}.toString(){{/isString}}){{/pathParams}}; @@ -128,6 +129,7 @@ class {{{classname}}} { headerParams, formParams, contentTypes.isEmpty ? null : contentTypes.first, + abortTrigger: abortTrigger, ); } @@ -161,8 +163,8 @@ class {{{classname}}} { /// {{/-last}} {{/allParams}} - Future<{{#returnType}}{{{.}}}?{{/returnType}}{{^returnType}}void{{/returnType}}> {{{nickname}}}({{#allParams}}{{#required}}{{{dataType}}} {{{paramName}}},{{^-last}} {{/-last}}{{/required}}{{/allParams}}{{#hasOptionalParams}}{ {{#allParams}}{{^required}}{{{dataType}}}? {{{paramName}}},{{^-last}} {{/-last}}{{/required}}{{/allParams}} }{{/hasOptionalParams}}) async { - final response = await {{{nickname}}}WithHttpInfo({{#allParams}}{{#required}}{{{paramName}}},{{^-last}} {{/-last}}{{/required}}{{/allParams}}{{#hasOptionalParams}} {{#allParams}}{{^required}}{{{paramName}}}: {{{paramName}}},{{^-last}} {{/-last}}{{/required}}{{/allParams}} {{/hasOptionalParams}}); + Future<{{#returnType}}{{{.}}}?{{/returnType}}{{^returnType}}void{{/returnType}}> {{{nickname}}}({{#allParams}}{{#required}}{{{dataType}}} {{{paramName}}}, {{/required}}{{/allParams}}{ {{#allParams}}{{^required}}{{{dataType}}}? {{{paramName}}}, {{/required}}{{/allParams}}Future? abortTrigger, }) async { + final response = await {{{nickname}}}WithHttpInfo({{#allParams}}{{#required}}{{{paramName}}}, {{/required}}{{/allParams}}{{#allParams}}{{^required}}{{{paramName}}}: {{{paramName}}}, {{/required}}{{/allParams}}abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } diff --git a/open-api/templates/mobile/api.mustache.patch b/open-api/templates/mobile/api.mustache.patch index e3f888d6d7..feb5f40047 100644 --- a/open-api/templates/mobile/api.mustache.patch +++ b/open-api/templates/mobile/api.mustache.patch @@ -1,8 +1,11 @@ ---- api.mustache 2025-01-22 05:50:25 -+++ api.mustache.modified 2025-01-22 05:52:23 -@@ -51,7 +51,7 @@ +--- api.mustache ++++ api.mustache.modified +@@ -49,9 +49,9 @@ + /// + {{/-last}} {{/allParams}} - Future {{{nickname}}}WithHttpInfo({{#allParams}}{{#required}}{{{dataType}}} {{{paramName}}},{{^-last}} {{/-last}}{{/required}}{{/allParams}}{{#hasOptionalParams}}{ {{#allParams}}{{^required}}{{{dataType}}}? {{{paramName}}},{{^-last}} {{/-last}}{{/required}}{{/allParams}} }{{/hasOptionalParams}}) async { +- Future {{{nickname}}}WithHttpInfo({{#allParams}}{{#required}}{{{dataType}}} {{{paramName}}},{{^-last}} {{/-last}}{{/required}}{{/allParams}}{{#hasOptionalParams}}{ {{#allParams}}{{^required}}{{{dataType}}}? {{{paramName}}},{{^-last}} {{/-last}}{{/required}}{{/allParams}} }{{/hasOptionalParams}}) async { ++ Future {{{nickname}}}WithHttpInfo({{#allParams}}{{#required}}{{{dataType}}} {{{paramName}}}, {{/required}}{{/allParams}}{ {{#allParams}}{{^required}}{{{dataType}}}? {{{paramName}}}, {{/required}}{{/allParams}}Future? abortTrigger, }) async { // ignore: prefer_const_declarations - final path = r'{{{path}}}'{{#pathParams}} + final apiPath = r'{{{path}}}'{{#pathParams}} @@ -18,7 +21,7 @@ {{#formParams}} {{^isFile}} if ({{{paramName}}} != null) { -@@ -121,7 +121,7 @@ +@@ -121,13 +121,14 @@ {{/isMultipart}} return apiClient.invokeAPI( @@ -27,3 +30,21 @@ '{{{httpMethod}}}', queryParams, postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, ++ abortTrigger: abortTrigger, + ); + } + +@@ -161,8 +162,8 @@ + /// + {{/-last}} + {{/allParams}} +- Future<{{#returnType}}{{{.}}}?{{/returnType}}{{^returnType}}void{{/returnType}}> {{{nickname}}}({{#allParams}}{{#required}}{{{dataType}}} {{{paramName}}},{{^-last}} {{/-last}}{{/required}}{{/allParams}}{{#hasOptionalParams}}{ {{#allParams}}{{^required}}{{{dataType}}}? {{{paramName}}},{{^-last}} {{/-last}}{{/required}}{{/allParams}} }{{/hasOptionalParams}}) async { +- final response = await {{{nickname}}}WithHttpInfo({{#allParams}}{{#required}}{{{paramName}}},{{^-last}} {{/-last}}{{/required}}{{/allParams}}{{#hasOptionalParams}} {{#allParams}}{{^required}}{{{paramName}}}: {{{paramName}}},{{^-last}} {{/-last}}{{/required}}{{/allParams}} {{/hasOptionalParams}}); ++ Future<{{#returnType}}{{{.}}}?{{/returnType}}{{^returnType}}void{{/returnType}}> {{{nickname}}}({{#allParams}}{{#required}}{{{dataType}}} {{{paramName}}}, {{/required}}{{/allParams}}{ {{#allParams}}{{^required}}{{{dataType}}}? {{{paramName}}}, {{/required}}{{/allParams}}Future? abortTrigger, }) async { ++ final response = await {{{nickname}}}WithHttpInfo({{#allParams}}{{#required}}{{{paramName}}}, {{/required}}{{/allParams}}{{#allParams}}{{^required}}{{{paramName}}}: {{{paramName}}}, {{/required}}{{/allParams}}abortTrigger: abortTrigger,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } diff --git a/open-api/templates/mobile/serialization/native/native_class.mustache b/open-api/templates/mobile/serialization/native/native_class.mustache index 2d6e6d24f3..973c5d9462 100644 --- a/open-api/templates/mobile/serialization/native/native_class.mustache +++ b/open-api/templates/mobile/serialization/native/native_class.mustache @@ -1,5 +1,6 @@ class {{{classname}}} { {{>dart_constructor}} + {{#vars}} {{#description}} /// {{{.}}} @@ -32,7 +33,17 @@ class {{{classname}}} { {{/required}} {{/isNullable}} {{/isEnum}} - {{#isArray}}{{#uniqueItems}}Set{{/uniqueItems}}{{^uniqueItems}}List{{/uniqueItems}}<{{{items.dataType}}}{{#items.isNullable}}?{{/items.isNullable}}>{{/isArray}}{{^isArray}}{{{datatypeWithEnum}}}{{/isArray}}{{#isNullable}}?{{/isNullable}}{{^isNullable}}{{^required}}{{^defaultValue}}?{{/defaultValue}}{{/required}}{{/isNullable}} {{{name}}}; + {{#required}} + {{{datatypeWithEnum}}}{{#isNullable}}?{{/isNullable}} {{{name}}}; + {{/required}} + {{^required}} + {{#vendorExtensions.x-is-optional}} + {{{datatypeWithEnum}}} {{{name}}}; + {{/vendorExtensions.x-is-optional}} + {{^vendorExtensions.x-is-optional}} + {{{datatypeWithEnum}}}{{#isNullable}}?{{/isNullable}}{{^isNullable}}{{^defaultValue}}?{{/defaultValue}}{{/isNullable}} {{{name}}}; + {{/vendorExtensions.x-is-optional}} + {{/required}} {{/vars}} @override @@ -54,6 +65,37 @@ class {{{classname}}} { Map toJson() { final json = {}; {{#vars}} + {{#vendorExtensions.x-is-optional}} + if (this.{{{name}}}.isPresent) { + final value = this.{{{name}}}.value; + {{#isDateTime}} + {{#pattern}} + json[r'{{{baseName}}}'] = value == null ? null : (_isEpochMarker(r'{{{pattern}}}') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); + {{/pattern}} + {{^pattern}} + json[r'{{{baseName}}}'] = value == null ? null : value.toUtc().toIso8601String(); + {{/pattern}} + {{/isDateTime}} + {{#isDate}} + {{#pattern}} + json[r'{{{baseName}}}'] = value == null ? null : (_isEpochMarker(r'{{{pattern}}}') + ? value.millisecondsSinceEpoch + : _dateFormatter.format(value.toUtc())); + {{/pattern}} + {{^pattern}} + json[r'{{{baseName}}}'] = value == null ? null : _dateFormatter.format(value.toUtc()); + {{/pattern}} + {{/isDate}} + {{^isDateTime}} + {{^isDate}} + json[r'{{{baseName}}}'] = value{{#isArray}}{{#uniqueItems}} == null ? null : value.toList(growable: false){{/uniqueItems}}{{/isArray}}; + {{/isDate}} + {{/isDateTime}} + } + {{/vendorExtensions.x-is-optional}} + {{^vendorExtensions.x-is-optional}} {{#isNullable}} if (this.{{{name}}} != null) { {{/isNullable}} @@ -91,18 +133,19 @@ class {{{classname}}} { {{/isDateTime}} {{#isNullable}} } else { - // json[r'{{{baseName}}}'] = null; + json[r'{{{baseName}}}'] = null; } {{/isNullable}} {{^isNullable}} {{^required}} {{^defaultValue}} } else { - // json[r'{{{baseName}}}'] = null; + json[r'{{{baseName}}}'] = null; } {{/defaultValue}} {{/required}} {{/isNullable}} + {{/vendorExtensions.x-is-optional}} {{/vars}} return json; } @@ -118,58 +161,118 @@ class {{{classname}}} { return {{{classname}}}( {{#vars}} {{#isDateTime}} + {{#vendorExtensions.x-is-optional}} + {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present(mapDateTime(json, r'{{{baseName}}}', r'{{{pattern}}}')) : const Optional.absent(), + {{/vendorExtensions.x-is-optional}} + {{^vendorExtensions.x-is-optional}} {{{name}}}: mapDateTime(json, r'{{{baseName}}}', r'{{{pattern}}}'){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}}, + {{/vendorExtensions.x-is-optional}} {{/isDateTime}} {{#isDate}} + {{#vendorExtensions.x-is-optional}} + {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present(mapDateTime(json, r'{{{baseName}}}', r'{{{pattern}}}')) : const Optional.absent(), + {{/vendorExtensions.x-is-optional}} + {{^vendorExtensions.x-is-optional}} {{{name}}}: mapDateTime(json, r'{{{baseName}}}', r'{{{pattern}}}'){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}}, + {{/vendorExtensions.x-is-optional}} {{/isDate}} {{^isDateTime}} {{^isDate}} {{#complexType}} {{#isArray}} {{#items.isArray}} + {{#vendorExtensions.x-is-optional}} + {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present(json[r'{{{baseName}}}'] is List + ? (json[r'{{{baseName}}}'] as List).map((e) => + {{#items.complexType}} + e == null ? {{#items.isNullable}}null{{/items.isNullable}}{{^items.isNullable}}const <{{items.complexType}}>[]{{/items.isNullable}} : {{items.complexType}}.listFromJson(e){{#uniqueItems}}.toSet(){{/uniqueItems}} + {{/items.complexType}} + {{^items.complexType}} + e == null ? {{#items.isNullable}}null{{/items.isNullable}}{{^items.isNullable}}const <{{items.items.dataType}}{{#items.items.isNullable}}?{{/items.items.isNullable}}>[]{{/items.isNullable}} : (e as List).map((value) => value as {{items.items.dataType}}{{#items.items.isNullable}}?{{/items.items.isNullable}}).toList(growable: false) + {{/items.complexType}} + ).toList() + : {{#isNullable}}null{{/isNullable}}{{^isNullable}}const []{{/isNullable}}) : const Optional.absent(), + {{/vendorExtensions.x-is-optional}} + {{^vendorExtensions.x-is-optional}} {{{name}}}: json[r'{{{baseName}}}'] is List ? (json[r'{{{baseName}}}'] as List).map((e) => {{#items.complexType}} - {{items.complexType}}.listFromJson(json[r'{{{baseName}}}']){{#uniqueItems}}.toSet(){{/uniqueItems}} + e == null ? {{#items.isNullable}}null{{/items.isNullable}}{{^items.isNullable}}const <{{items.complexType}}>[]{{/items.isNullable}} : {{items.complexType}}.listFromJson(e){{#uniqueItems}}.toSet(){{/uniqueItems}} {{/items.complexType}} {{^items.complexType}} - e == null ? {{#items.isNullable}}null{{/items.isNullable}}{{^items.isNullable}}const <{{items.items.dataType}}>[]{{/items.isNullable}} : (e as List).cast<{{items.items.dataType}}>() + e == null ? {{#items.isNullable}}null{{/items.isNullable}}{{^items.isNullable}}const <{{items.items.dataType}}{{#items.items.isNullable}}?{{/items.items.isNullable}}>[]{{/items.isNullable}} : (e as List).map((value) => value as {{items.items.dataType}}{{#items.items.isNullable}}?{{/items.items.isNullable}}).toList(growable: false) {{/items.complexType}} ).toList() : {{#isNullable}}null{{/isNullable}}{{^isNullable}}const []{{/isNullable}}, + {{/vendorExtensions.x-is-optional}} {{/items.isArray}} {{^items.isArray}} + {{#vendorExtensions.x-is-optional}} + {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present({{{complexType}}}.listFromJson(json[r'{{{baseName}}}']){{#uniqueItems}}.toSet(){{/uniqueItems}}) : const Optional.absent(), + {{/vendorExtensions.x-is-optional}} + {{^vendorExtensions.x-is-optional}} {{{name}}}: {{{complexType}}}.listFromJson(json[r'{{{baseName}}}']){{#uniqueItems}}.toSet(){{/uniqueItems}}, + {{/vendorExtensions.x-is-optional}} {{/items.isArray}} {{/isArray}} {{^isArray}} {{#isMap}} {{#items.isArray}} + {{#vendorExtensions.x-is-optional}} + {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present(json[r'{{{baseName}}}'] == null ? null + {{#items.complexType}} + : {{items.complexType}}.mapListFromJson(json[r'{{{baseName}}}'])) : const Optional.absent(), + {{/items.complexType}} + {{^items.complexType}} + : (json[r'{{{baseName}}}'] as Map).map((k, v) => MapEntry(k, v == null ? {{#items.isNullable}}null{{/items.isNullable}}{{^items.isNullable}}const <{{items.items.dataType}}{{#items.items.isNullable}}?{{/items.items.isNullable}}>[]{{/items.isNullable}} : (v as List).map((value) => value as {{items.items.dataType}}{{#items.items.isNullable}}?{{/items.items.isNullable}}).toList(growable: false)))) : const Optional.absent(), + {{/items.complexType}} + {{/vendorExtensions.x-is-optional}} + {{^vendorExtensions.x-is-optional}} {{{name}}}: json[r'{{{baseName}}}'] == null ? {{#defaultValue}}{{{.}}}{{/defaultValue}}{{^defaultValue}}null{{/defaultValue}} - {{#items.complexType}} + {{#items.complexType}} : {{items.complexType}}.mapListFromJson(json[r'{{{baseName}}}']), - {{/items.complexType}} - {{^items.complexType}} - : mapCastOfType(json, r'{{{baseName}}}'), - {{/items.complexType}} + {{/items.complexType}} + {{^items.complexType}} + : (json[r'{{{baseName}}}'] as Map).map((k, v) => MapEntry(k, v == null ? {{#items.isNullable}}null{{/items.isNullable}}{{^items.isNullable}}const <{{items.items.dataType}}{{#items.items.isNullable}}?{{/items.items.isNullable}}>[]{{/items.isNullable}} : (v as List).map((value) => value as {{items.items.dataType}}{{#items.items.isNullable}}?{{/items.items.isNullable}}).toList(growable: false))), + {{/items.complexType}} + {{/vendorExtensions.x-is-optional}} {{/items.isArray}} {{^items.isArray}} {{#items.isMap}} {{#items.complexType}} + {{#vendorExtensions.x-is-optional}} + {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present({{items.complexType}}.mapFromJson(json[r'{{{baseName}}}'])) : const Optional.absent(), + {{/vendorExtensions.x-is-optional}} + {{^vendorExtensions.x-is-optional}} {{{name}}}: {{items.complexType}}.mapFromJson(json[r'{{{baseName}}}']), + {{/vendorExtensions.x-is-optional}} {{/items.complexType}} {{^items.complexType}} + {{#vendorExtensions.x-is-optional}} + {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present(mapCastOfType(json, r'{{{baseName}}}')) : const Optional.absent(), + {{/vendorExtensions.x-is-optional}} + {{^vendorExtensions.x-is-optional}} {{{name}}}: mapCastOfType(json, r'{{{baseName}}}'){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}}, + {{/vendorExtensions.x-is-optional}} {{/items.complexType}} {{/items.isMap}} {{^items.isMap}} {{#items.complexType}} + {{#vendorExtensions.x-is-optional}} + {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present({{{items.complexType}}}.mapFromJson(json[r'{{{baseName}}}'])) : const Optional.absent(), + {{/vendorExtensions.x-is-optional}} + {{^vendorExtensions.x-is-optional}} {{{name}}}: {{{items.complexType}}}.mapFromJson(json[r'{{{baseName}}}']), + {{/vendorExtensions.x-is-optional}} {{/items.complexType}} {{^items.complexType}} + {{#vendorExtensions.x-is-optional}} + {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present(mapCastOfType(json, r'{{{baseName}}}')) : const Optional.absent(), + {{/vendorExtensions.x-is-optional}} + {{^vendorExtensions.x-is-optional}} {{{name}}}: mapCastOfType(json, r'{{{baseName}}}'){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}}, + {{/vendorExtensions.x-is-optional}} {{/items.complexType}} {{/items.isMap}} {{/items.isArray}} @@ -179,7 +282,12 @@ class {{{classname}}} { {{{name}}}: null, // No support for decoding binary content from JSON {{/isBinary}} {{^isBinary}} + {{#vendorExtensions.x-is-optional}} + {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present({{{complexType}}}.fromJson(json[r'{{{baseName}}}'])) : const Optional.absent(), + {{/vendorExtensions.x-is-optional}} + {{^vendorExtensions.x-is-optional}} {{{name}}}: {{{complexType}}}.fromJson(json[r'{{{baseName}}}']){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}}, + {{/vendorExtensions.x-is-optional}} {{/isBinary}} {{/isMap}} {{/isArray}} @@ -187,37 +295,74 @@ class {{{classname}}} { {{^complexType}} {{#isArray}} {{#isEnum}} + {{#vendorExtensions.x-is-optional}} + {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present({{{items.datatypeWithEnum}}}.listFromJson(json[r'{{{baseName}}}']){{#uniqueItems}}.toSet(){{/uniqueItems}}) : const Optional.absent(), + {{/vendorExtensions.x-is-optional}} + {{^vendorExtensions.x-is-optional}} {{{name}}}: {{{items.datatypeWithEnum}}}.listFromJson(json[r'{{{baseName}}}']){{#uniqueItems}}.toSet(){{/uniqueItems}}, + {{/vendorExtensions.x-is-optional}} {{/isEnum}} {{^isEnum}} + {{#vendorExtensions.x-is-optional}} + {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present(json[r'{{{baseName}}}'] is Iterable + ? (json[r'{{{baseName}}}'] as Iterable).cast<{{{items.datatype}}}>().{{#uniqueItems}}toSet(){{/uniqueItems}}{{^uniqueItems}}toList(growable: false){{/uniqueItems}} + : {{#defaultValue}}{{{.}}}{{/defaultValue}}{{^defaultValue}}null{{/defaultValue}}) : const Optional.absent(), + {{/vendorExtensions.x-is-optional}} + {{^vendorExtensions.x-is-optional}} {{{name}}}: json[r'{{{baseName}}}'] is Iterable ? (json[r'{{{baseName}}}'] as Iterable).cast<{{{items.datatype}}}>().{{#uniqueItems}}toSet(){{/uniqueItems}}{{^uniqueItems}}toList(growable: false){{/uniqueItems}} : {{#defaultValue}}{{{.}}}{{/defaultValue}}{{^defaultValue}}null{{/defaultValue}}, + {{/vendorExtensions.x-is-optional}} {{/isEnum}} {{/isArray}} {{^isArray}} {{#isMap}} + {{#vendorExtensions.x-is-optional}} + {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present(mapCastOfType(json, r'{{{baseName}}}')) : const Optional.absent(), + {{/vendorExtensions.x-is-optional}} + {{^vendorExtensions.x-is-optional}} {{{name}}}: mapCastOfType(json, r'{{{baseName}}}'){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}}, + {{/vendorExtensions.x-is-optional}} {{/isMap}} {{^isMap}} {{#isNumber}} + {{#vendorExtensions.x-is-optional}} + {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present(json[r'{{{baseName}}}'] == null ? null : num.parse('${json[r'{{{baseName}}}']}')) : const Optional.absent(), + {{/vendorExtensions.x-is-optional}} + {{^vendorExtensions.x-is-optional}} {{{name}}}: {{#isNullable}}json[r'{{{baseName}}}'] == null ? {{#defaultValue}}{{{.}}}{{/defaultValue}}{{^defaultValue}}null{{/defaultValue}} : {{/isNullable}}{{{datatypeWithEnum}}}.parse('${json[r'{{{baseName}}}']}'), + {{/vendorExtensions.x-is-optional}} {{/isNumber}} - {{#isDouble}} - {{{name}}}: (mapValueOfType(json, r'{{{baseName}}}'){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}}){{#isNullable}}?{{/isNullable}}.toDouble(), - {{/isDouble}} - {{^isDouble}} {{^isNumber}} - {{^isEnum}} + {{#vendorExtensions.x-original-is-integer}} + {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present(json[r'{{{baseName}}}'] == null ? null : int.parse('${json[r'{{{baseName}}}']}')) : const Optional.absent(), + {{/vendorExtensions.x-original-is-integer}} + {{^vendorExtensions.x-original-is-integer}} + {{#vendorExtensions.x-original-is-number}} + {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present(json[r'{{{baseName}}}'] == null ? null : num.parse('${json[r'{{{baseName}}}']}')) : const Optional.absent(), + {{/vendorExtensions.x-original-is-number}} + {{^vendorExtensions.x-original-is-number}} + {{^isEnum}} + {{#vendorExtensions.x-is-optional}} + {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present(mapValueOfType<{{#vendorExtensions.x-unwrapped-datatype}}{{{vendorExtensions.x-unwrapped-datatype}}}{{/vendorExtensions.x-unwrapped-datatype}}{{^vendorExtensions.x-unwrapped-datatype}}{{{datatypeWithEnum}}}{{/vendorExtensions.x-unwrapped-datatype}}>(json, r'{{{baseName}}}')) : const Optional.absent(), + {{/vendorExtensions.x-is-optional}} + {{^vendorExtensions.x-is-optional}} {{{name}}}: mapValueOfType<{{{datatypeWithEnum}}}>(json, r'{{{baseName}}}'){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}}, - {{/isEnum}} - {{#isEnum}} - {{{name}}}: {{{enumName}}}.fromJson(json[r'{{{baseName}}}']){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}}, - {{/isEnum}} + {{/vendorExtensions.x-is-optional}} + {{/isEnum}} + {{#isEnum}} + {{#vendorExtensions.x-is-optional}} + {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present({{{enumName}}}.fromJson(json[r'{{{baseName}}}'])) : const Optional.absent(), + {{/vendorExtensions.x-is-optional}} + {{^vendorExtensions.x-is-optional}} + {{{name}}}: {{{enumName}}}.fromJson(json[r'{{{baseName}}}']){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? const {{{enumName}}}._({{{.}}}){{/defaultValue}}{{/required}}, + {{/vendorExtensions.x-is-optional}} + {{/isEnum}} + {{/vendorExtensions.x-original-is-number}} + {{/vendorExtensions.x-original-is-integer}} {{/isNumber}} - {{/isDouble}} {{/isMap}} {{/isArray}} {{/complexType}} @@ -284,11 +429,13 @@ class {{{classname}}} { {{^isContainer}} {{>serialization/native/native_enum_inline}} + {{/isContainer}} {{#isContainer}} {{#mostInnerItems}} {{>serialization/native/native_enum_inline}} + {{/mostInnerItems}} {{/isContainer}} {{/isEnum}} diff --git a/open-api/templates/mobile/serialization/native/native_class.mustache.patch b/open-api/templates/mobile/serialization/native/native_class.mustache.patch index 8eeefdad97..6ab7a5228f 100644 --- a/open-api/templates/mobile/serialization/native/native_class.mustache.patch +++ b/open-api/templates/mobile/serialization/native/native_class.mustache.patch @@ -1,60 +1,180 @@ ---- native_class.mustache 2025-07-01 08:29:23.968133163 +0800 -+++ native_class_temp.mustache 2025-07-01 08:29:44.225850583 +0800 -@@ -91,14 +91,14 @@ - {{/isDateTime}} - {{#isNullable}} - } else { -- json[r'{{{baseName}}}'] = null; -+ // json[r'{{{baseName}}}'] = null; - } - {{/isNullable}} - {{^isNullable}} - {{^required}} - {{^defaultValue}} - } else { -- json[r'{{{baseName}}}'] = null; -+ // json[r'{{{baseName}}}'] = null; - } - {{/defaultValue}} - {{/required}} -@@ -111,20 +111,10 @@ +--- native_class.mustache 2026-06-03 12:23:39 ++++ native_class.mustache 2026-06-03 12:21:49 +@@ -154,24 +154,10 @@ /// [value] if it's a [Map], null otherwise. // ignore: prefer_constructors_over_static_methods static {{{classname}}}? fromJson(dynamic value) { + upgradeDto(value, "{{{classname}}}"); if (value is Map) { final json = value.cast(); - + - // Ensure that the map contains the required keys. - // Note 1: the values aren't checked for validity beyond being non-null. - // Note 2: this code is stripped in release mode! - assert(() { -- requiredKeys.forEach((key) { -- assert(json.containsKey(key), 'Required key "{{{classname}}}[$key]" is missing from JSON.'); -- assert(json[key] != null, 'Required key "{{{classname}}}[$key]" has a null value in JSON.'); -- }); +- {{#vars}} +- {{#required}} +- assert(json.containsKey(r'{{{baseName}}}'), 'Required key "{{{classname}}}[{{{baseName}}}]" is missing from JSON.'); +- {{^isNullable}} +- assert(json[r'{{{baseName}}}'] != null, 'Required key "{{{classname}}}[{{{baseName}}}]" has a null value in JSON.'); +- {{/isNullable}} +- {{/required}} +- {{/vars}} - return true; - }()); - return {{{classname}}}( {{#vars}} {{#isDateTime}} -@@ -215,6 +205,10 @@ +@@ -195,48 +181,98 @@ + {{#complexType}} + {{#isArray}} + {{#items.isArray}} ++ {{#vendorExtensions.x-is-optional}} ++ {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present(json[r'{{{baseName}}}'] is List ++ ? (json[r'{{{baseName}}}'] as List).map((e) => ++ {{#items.complexType}} ++ e == null ? {{#items.isNullable}}null{{/items.isNullable}}{{^items.isNullable}}const <{{items.complexType}}>[]{{/items.isNullable}} : {{items.complexType}}.listFromJson(e){{#uniqueItems}}.toSet(){{/uniqueItems}} ++ {{/items.complexType}} ++ {{^items.complexType}} ++ e == null ? {{#items.isNullable}}null{{/items.isNullable}}{{^items.isNullable}}const <{{items.items.dataType}}{{#items.items.isNullable}}?{{/items.items.isNullable}}>[]{{/items.isNullable}} : (e as List).map((value) => value as {{items.items.dataType}}{{#items.items.isNullable}}?{{/items.items.isNullable}}).toList(growable: false) ++ {{/items.complexType}} ++ ).toList() ++ : {{#isNullable}}null{{/isNullable}}{{^isNullable}}const []{{/isNullable}}) : const Optional.absent(), ++ {{/vendorExtensions.x-is-optional}} ++ {{^vendorExtensions.x-is-optional}} + {{{name}}}: json[r'{{{baseName}}}'] is List + ? (json[r'{{{baseName}}}'] as List).map((e) => + {{#items.complexType}} +- {{items.complexType}}.listFromJson(json[r'{{{baseName}}}']){{#uniqueItems}}.toSet(){{/uniqueItems}} ++ e == null ? {{#items.isNullable}}null{{/items.isNullable}}{{^items.isNullable}}const <{{items.complexType}}>[]{{/items.isNullable}} : {{items.complexType}}.listFromJson(e){{#uniqueItems}}.toSet(){{/uniqueItems}} + {{/items.complexType}} + {{^items.complexType}} +- e == null ? {{#items.isNullable}}null{{/items.isNullable}}{{^items.isNullable}}const <{{items.items.dataType}}>[]{{/items.isNullable}} : (e as List).cast<{{items.items.dataType}}>() ++ e == null ? {{#items.isNullable}}null{{/items.isNullable}}{{^items.isNullable}}const <{{items.items.dataType}}{{#items.items.isNullable}}?{{/items.items.isNullable}}>[]{{/items.isNullable}} : (e as List).map((value) => value as {{items.items.dataType}}{{#items.items.isNullable}}?{{/items.items.isNullable}}).toList(growable: false) + {{/items.complexType}} + ).toList() + : {{#isNullable}}null{{/isNullable}}{{^isNullable}}const []{{/isNullable}}, ++ {{/vendorExtensions.x-is-optional}} + {{/items.isArray}} + {{^items.isArray}} ++ {{#vendorExtensions.x-is-optional}} ++ {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present({{{complexType}}}.listFromJson(json[r'{{{baseName}}}']){{#uniqueItems}}.toSet(){{/uniqueItems}}) : const Optional.absent(), ++ {{/vendorExtensions.x-is-optional}} ++ {{^vendorExtensions.x-is-optional}} + {{{name}}}: {{{complexType}}}.listFromJson(json[r'{{{baseName}}}']){{#uniqueItems}}.toSet(){{/uniqueItems}}, ++ {{/vendorExtensions.x-is-optional}} + {{/items.isArray}} + {{/isArray}} + {{^isArray}} + {{#isMap}} + {{#items.isArray}} ++ {{#vendorExtensions.x-is-optional}} ++ {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present(json[r'{{{baseName}}}'] == null ? null ++ {{#items.complexType}} ++ : {{items.complexType}}.mapListFromJson(json[r'{{{baseName}}}'])) : const Optional.absent(), ++ {{/items.complexType}} ++ {{^items.complexType}} ++ : (json[r'{{{baseName}}}'] as Map).map((k, v) => MapEntry(k, v == null ? {{#items.isNullable}}null{{/items.isNullable}}{{^items.isNullable}}const <{{items.items.dataType}}{{#items.items.isNullable}}?{{/items.items.isNullable}}>[]{{/items.isNullable}} : (v as List).map((value) => value as {{items.items.dataType}}{{#items.items.isNullable}}?{{/items.items.isNullable}}).toList(growable: false)))) : const Optional.absent(), ++ {{/items.complexType}} ++ {{/vendorExtensions.x-is-optional}} ++ {{^vendorExtensions.x-is-optional}} + {{{name}}}: json[r'{{{baseName}}}'] == null + ? {{#defaultValue}}{{{.}}}{{/defaultValue}}{{^defaultValue}}null{{/defaultValue}} +- {{#items.complexType}} ++ {{#items.complexType}} + : {{items.complexType}}.mapListFromJson(json[r'{{{baseName}}}']), +- {{/items.complexType}} +- {{^items.complexType}} +- : (json[r'{{{baseName}}}'] as Map).map((k, v) => MapEntry(k, v == null ? {{#items.isNullable}}null{{/items.isNullable}}{{^items.isNullable}}const <{{items.items.dataType}}>[]{{/items.isNullable}} : (v as List).cast<{{items.items.dataType}}>())), +- {{/items.complexType}} ++ {{/items.complexType}} ++ {{^items.complexType}} ++ : (json[r'{{{baseName}}}'] as Map).map((k, v) => MapEntry(k, v == null ? {{#items.isNullable}}null{{/items.isNullable}}{{^items.isNullable}}const <{{items.items.dataType}}{{#items.items.isNullable}}?{{/items.items.isNullable}}>[]{{/items.isNullable}} : (v as List).map((value) => value as {{items.items.dataType}}{{#items.items.isNullable}}?{{/items.items.isNullable}}).toList(growable: false))), ++ {{/items.complexType}} ++ {{/vendorExtensions.x-is-optional}} + {{/items.isArray}} + {{^items.isArray}} + {{#items.isMap}} + {{#items.complexType}} ++ {{#vendorExtensions.x-is-optional}} ++ {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present({{items.complexType}}.mapFromJson(json[r'{{{baseName}}}'])) : const Optional.absent(), ++ {{/vendorExtensions.x-is-optional}} ++ {{^vendorExtensions.x-is-optional}} + {{{name}}}: {{items.complexType}}.mapFromJson(json[r'{{{baseName}}}']), ++ {{/vendorExtensions.x-is-optional}} + {{/items.complexType}} + {{^items.complexType}} ++ {{#vendorExtensions.x-is-optional}} ++ {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present(mapCastOfType(json, r'{{{baseName}}}')) : const Optional.absent(), ++ {{/vendorExtensions.x-is-optional}} ++ {{^vendorExtensions.x-is-optional}} + {{{name}}}: mapCastOfType(json, r'{{{baseName}}}'){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}}, ++ {{/vendorExtensions.x-is-optional}} + {{/items.complexType}} + {{/items.isMap}} + {{^items.isMap}} + {{#items.complexType}} ++ {{#vendorExtensions.x-is-optional}} ++ {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present({{{items.complexType}}}.mapFromJson(json[r'{{{baseName}}}'])) : const Optional.absent(), ++ {{/vendorExtensions.x-is-optional}} ++ {{^vendorExtensions.x-is-optional}} + {{{name}}}: {{{items.complexType}}}.mapFromJson(json[r'{{{baseName}}}']), ++ {{/vendorExtensions.x-is-optional}} + {{/items.complexType}} + {{^items.complexType}} ++ {{#vendorExtensions.x-is-optional}} ++ {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present(mapCastOfType(json, r'{{{baseName}}}')) : const Optional.absent(), ++ {{/vendorExtensions.x-is-optional}} ++ {{^vendorExtensions.x-is-optional}} + {{{name}}}: mapCastOfType(json, r'{{{baseName}}}'){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}}, ++ {{/vendorExtensions.x-is-optional}} + {{/items.complexType}} + {{/items.isMap}} + {{/items.isArray}} +@@ -259,23 +295,45 @@ + {{^complexType}} + {{#isArray}} + {{#isEnum}} ++ {{#vendorExtensions.x-is-optional}} ++ {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present({{{items.datatypeWithEnum}}}.listFromJson(json[r'{{{baseName}}}']){{#uniqueItems}}.toSet(){{/uniqueItems}}) : const Optional.absent(), ++ {{/vendorExtensions.x-is-optional}} ++ {{^vendorExtensions.x-is-optional}} + {{{name}}}: {{{items.datatypeWithEnum}}}.listFromJson(json[r'{{{baseName}}}']){{#uniqueItems}}.toSet(){{/uniqueItems}}, ++ {{/vendorExtensions.x-is-optional}} + {{/isEnum}} + {{^isEnum}} ++ {{#vendorExtensions.x-is-optional}} ++ {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present(json[r'{{{baseName}}}'] is Iterable ++ ? (json[r'{{{baseName}}}'] as Iterable).cast<{{{items.datatype}}}>().{{#uniqueItems}}toSet(){{/uniqueItems}}{{^uniqueItems}}toList(growable: false){{/uniqueItems}} ++ : {{#defaultValue}}{{{.}}}{{/defaultValue}}{{^defaultValue}}null{{/defaultValue}}) : const Optional.absent(), ++ {{/vendorExtensions.x-is-optional}} ++ {{^vendorExtensions.x-is-optional}} + {{{name}}}: json[r'{{{baseName}}}'] is Iterable + ? (json[r'{{{baseName}}}'] as Iterable).cast<{{{items.datatype}}}>().{{#uniqueItems}}toSet(){{/uniqueItems}}{{^uniqueItems}}toList(growable: false){{/uniqueItems}} + : {{#defaultValue}}{{{.}}}{{/defaultValue}}{{^defaultValue}}null{{/defaultValue}}, ++ {{/vendorExtensions.x-is-optional}} + {{/isEnum}} + {{/isArray}} + {{^isArray}} + {{#isMap}} ++ {{#vendorExtensions.x-is-optional}} ++ {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present(mapCastOfType(json, r'{{{baseName}}}')) : const Optional.absent(), ++ {{/vendorExtensions.x-is-optional}} ++ {{^vendorExtensions.x-is-optional}} + {{{name}}}: mapCastOfType(json, r'{{{baseName}}}'){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}}, ++ {{/vendorExtensions.x-is-optional}} + {{/isMap}} + {{^isMap}} + {{#isNumber}} ++ {{#vendorExtensions.x-is-optional}} ++ {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present(json[r'{{{baseName}}}'] == null ? null : num.parse('${json[r'{{{baseName}}}']}')) : const Optional.absent(), ++ {{/vendorExtensions.x-is-optional}} ++ {{^vendorExtensions.x-is-optional}} + {{{name}}}: {{#isNullable}}json[r'{{{baseName}}}'] == null ? {{#defaultValue}}{{{.}}}{{/defaultValue}}{{^defaultValue}}null{{/defaultValue}} : {{/isNullable}}{{{datatypeWithEnum}}}.parse('${json[r'{{{baseName}}}']}'), ++ {{/vendorExtensions.x-is-optional}} {{/isNumber}} -+ {{#isDouble}} -+ {{{name}}}: (mapValueOfType(json, r'{{{baseName}}}'){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}}){{#isNullable}}?{{/isNullable}}.toDouble(), -+ {{/isDouble}} -+ {{^isDouble}} {{^isNumber}} - {{^isEnum}} - {{{name}}}: mapValueOfType<{{{datatypeWithEnum}}}>(json, r'{{{baseName}}}'){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}}, -@@ -223,6 +217,7 @@ - {{{name}}}: {{{enumName}}}.fromJson(json[r'{{{baseName}}}']){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}}, - {{/isEnum}} - {{/isNumber}} -+ {{/isDouble}} - {{/isMap}} - {{/isArray}} - {{/complexType}} + {{#vendorExtensions.x-original-is-integer}} diff --git a/open-api/templates/mobile/serialization/native/native_class_nullable_items_in_arrays.patch b/open-api/templates/mobile/serialization/native/native_class_nullable_items_in_arrays.patch deleted file mode 100644 index a59e300913..0000000000 --- a/open-api/templates/mobile/serialization/native/native_class_nullable_items_in_arrays.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/open-api/templates/mobile/serialization/native/native_class.mustache b/open-api/templates/mobile/serialization/native/native_class.mustache -index 9a7b1439b..9f40d5b0b 100644 ---- a/open-api/templates/mobile/serialization/native/native_class.mustache -+++ b/open-api/templates/mobile/serialization/native/native_class.mustache -@@ -32,7 +32,7 @@ class {{{classname}}} { - {{/required}} - {{/isNullable}} - {{/isEnum}} -- {{{datatypeWithEnum}}}{{#isNullable}}?{{/isNullable}}{{^isNullable}}{{^required}}{{^defaultValue}}?{{/defaultValue}}{{/required}}{{/isNullable}} {{{name}}}; -+ {{#isArray}}{{#uniqueItems}}Set{{/uniqueItems}}{{^uniqueItems}}List{{/uniqueItems}}<{{{items.dataType}}}{{#items.isNullable}}?{{/items.isNullable}}>{{/isArray}}{{^isArray}}{{{datatypeWithEnum}}}{{/isArray}}{{#isNullable}}?{{/isNullable}}{{^isNullable}}{{^required}}{{^defaultValue}}?{{/defaultValue}}{{/required}}{{/isNullable}} {{{name}}}; - - {{/vars}} - @override diff --git a/open-api/typescript-sdk/.nvmrc b/open-api/typescript-sdk/.nvmrc deleted file mode 100644 index 5bf4400f22..0000000000 --- a/open-api/typescript-sdk/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -24.15.0 diff --git a/package.json b/package.json index abe9e6b44f..c6d1041f5a 100644 --- a/package.json +++ b/package.json @@ -3,8 +3,16 @@ "version": "2.7.5", "description": "Monorepo for Immich", "private": true, - "packageManager": "pnpm@10.33.1+sha512.05ba3c1d5d1c18f68df06470d74055e62d41fc110a0c660db1b2dfb2785327f04cf0f68345d4609bc52089e7fa0343c31593b2f9594e2c5d5da426230acc9820", + "scripts": { + "format": "prettier --cache --check i18n/", + "format:fix": "prettier --cache --write --list-different i18n" + }, + "packageManager": "pnpm@10.33.4+sha512.1c67b3b359b2d408119ba1ed289f34b8fc3c6873412bec6fd264fbdc82489e510fcbecb9ce9d22dae7f3b76269d8441046014bdca53b9979cd7a561ad631b800", "engines": { "pnpm": ">=10.0.0" + }, + "devDependencies": { + "prettier": "^3.8.3", + "prettier-plugin-sort-json": "^4.2.0" } } diff --git a/cli/.editorconfig b/packages/cli/.editorconfig similarity index 100% rename from cli/.editorconfig rename to packages/cli/.editorconfig diff --git a/cli/.gitignore b/packages/cli/.gitignore similarity index 100% rename from cli/.gitignore rename to packages/cli/.gitignore diff --git a/cli/.npmignore b/packages/cli/.npmignore similarity index 100% rename from cli/.npmignore rename to packages/cli/.npmignore diff --git a/cli/.prettierignore b/packages/cli/.prettierignore similarity index 100% rename from cli/.prettierignore rename to packages/cli/.prettierignore diff --git a/cli/.prettierrc b/packages/cli/.prettierrc similarity index 100% rename from cli/.prettierrc rename to packages/cli/.prettierrc diff --git a/packages/cli/Dockerfile b/packages/cli/Dockerfile new file mode 100644 index 0000000000..ee2a4294bd --- /dev/null +++ b/packages/cli/Dockerfile @@ -0,0 +1,12 @@ +FROM node:24.1.0-alpine3.20@sha256:8fe019e0d57dbdce5f5c27c0b63d2775cf34b00e3755a7dea969802d7e0c2b25 AS core + +WORKDIR /usr/src/app +COPY package* pnpm* .pnpmfile.cjs ./ +COPY ./packages ./packages/ +RUN corepack enable pnpm && \ + pnpm --filter @immich/sdk --filter @immich/cli install --frozen-lockfile && \ + pnpm --filter @immich/sdk --filter @immich/cli build + +WORKDIR /import + +ENTRYPOINT ["node", "/usr/src/app/packages/cli/dist"] diff --git a/cli/LICENSE b/packages/cli/LICENSE similarity index 100% rename from cli/LICENSE rename to packages/cli/LICENSE diff --git a/cli/README.md b/packages/cli/README.md similarity index 77% rename from cli/README.md rename to packages/cli/README.md index b9d61fce09..92582fccc4 100644 --- a/cli/README.md +++ b/packages/cli/README.md @@ -4,14 +4,9 @@ Please see the [Immich CLI documentation](https://docs.immich.app/features/comma # For developers -Before building the CLI, you must build the immich server and the open-api client. To build the server run the following in the server folder: +Before building the CLI, you must build the immich server and the open-api client. You can use the following command: - $ pnpm install - $ pnpm run build - -Then, to build the open-api client run the following in the open-api folder: - - $ ./bin/generate-open-api.sh + $ mise //:open-api ## Run from build diff --git a/cli/bin/immich b/packages/cli/bin/immich similarity index 100% rename from cli/bin/immich rename to packages/cli/bin/immich diff --git a/cli/eslint.config.mjs b/packages/cli/eslint.config.mjs similarity index 100% rename from cli/eslint.config.mjs rename to packages/cli/eslint.config.mjs diff --git a/cli/mise.toml b/packages/cli/mise.toml similarity index 57% rename from cli/mise.toml rename to packages/cli/mise.toml index 740184e03d..28d5e1858f 100644 --- a/cli/mise.toml +++ b/packages/cli/mise.toml @@ -7,7 +7,7 @@ run = "vite build" [tasks.test] env._.path = "./node_modules/.bin" -run = "vite" +run = "vitest" [tasks.lint] env._.path = "./node_modules/.bin" @@ -27,3 +27,26 @@ run = "prettier --write ." [tasks.check] env._.path = "./node_modules/.bin" run = "tsc --noEmit" + +[tasks.ci-publish] +depends = ["//:sdk:install", "//:sdk:build"] +run = [ + { task = ":install" }, + { task = ":build" }, + "pnpm publish --provenance --no-git-checks", +] + +[tasks.ci-unit] +depends = ["//:sdk:install", "//:sdk:build"] +run = [ + { task = ":install" }, + { task = ":format" }, + { task = ":lint" }, + { task = ":check" }, + { task = ":test --run" }, +] + +[tasks.checklist] +run = [ + { task = ":ci-unit" }, +] diff --git a/cli/package.json b/packages/cli/package.json similarity index 95% rename from cli/package.json rename to packages/cli/package.json index 7b42f77ff1..988cf034a5 100644 --- a/cli/package.json +++ b/packages/cli/package.json @@ -2,6 +2,11 @@ "name": "@immich/cli", "version": "2.7.5", "description": "Command Line Interface (CLI) for Immich", + "repository": { + "type": "git", + "url": "git+https://github.com/immich-app/immich.git", + "directory": "packages/cli" + }, "type": "module", "exports": "./dist/index.js", "bin": { @@ -20,7 +25,7 @@ "@types/lodash-es": "^4.17.12", "@types/micromatch": "^4.0.9", "@types/mock-fs": "^4.13.1", - "@types/node": "^24.12.2", + "@types/node": "^24.12.4", "@vitest/coverage-v8": "^4.0.0", "byte-size": "^9.0.0", "cli-progress": "^3.12.0", @@ -52,11 +57,6 @@ "format:fix": "prettier --cache --write --list-different .", "check": "tsc --noEmit" }, - "repository": { - "type": "git", - "url": "git+https://github.com/immich-app/immich.git", - "directory": "cli" - }, "engines": { "node": ">=20.0.0" }, @@ -66,8 +66,5 @@ "fastq": "^1.17.1", "lodash-es": "^4.17.21", "micromatch": "^4.0.8" - }, - "volta": { - "node": "24.15.0" } } diff --git a/cli/src/commands/asset.spec.ts b/packages/cli/src/commands/asset.spec.ts similarity index 100% rename from cli/src/commands/asset.spec.ts rename to packages/cli/src/commands/asset.spec.ts diff --git a/cli/src/commands/asset.ts b/packages/cli/src/commands/asset.ts similarity index 99% rename from cli/src/commands/asset.ts rename to packages/cli/src/commands/asset.ts index 2c6430c83a..b0a9037289 100644 --- a/cli/src/commands/asset.ts +++ b/packages/cli/src/commands/asset.ts @@ -426,6 +426,8 @@ const uploadFile = async (input: string, stats: Stats): Promise, body: formData, + // eslint-disable-next-line unicorn/no-null + window: null, }); if (response.status !== 200 && response.status !== 201) { throw new Error(await response.text()); diff --git a/cli/src/commands/auth.ts b/packages/cli/src/commands/auth.ts similarity index 100% rename from cli/src/commands/auth.ts rename to packages/cli/src/commands/auth.ts diff --git a/cli/src/commands/server-info.ts b/packages/cli/src/commands/server-info.ts similarity index 100% rename from cli/src/commands/server-info.ts rename to packages/cli/src/commands/server-info.ts diff --git a/cli/src/index.ts b/packages/cli/src/index.ts similarity index 100% rename from cli/src/index.ts rename to packages/cli/src/index.ts diff --git a/cli/src/queue.ts b/packages/cli/src/queue.ts similarity index 100% rename from cli/src/queue.ts rename to packages/cli/src/queue.ts diff --git a/cli/src/utils.spec.ts b/packages/cli/src/utils.spec.ts similarity index 100% rename from cli/src/utils.spec.ts rename to packages/cli/src/utils.spec.ts diff --git a/cli/src/utils.ts b/packages/cli/src/utils.ts similarity index 100% rename from cli/src/utils.ts rename to packages/cli/src/utils.ts diff --git a/cli/tsconfig.json b/packages/cli/tsconfig.json similarity index 100% rename from cli/tsconfig.json rename to packages/cli/tsconfig.json diff --git a/cli/vite.config.ts b/packages/cli/vite.config.ts similarity index 100% rename from cli/vite.config.ts rename to packages/cli/vite.config.ts diff --git a/e2e-auth-server/Dockerfile b/packages/e2e-auth-server/Dockerfile similarity index 100% rename from e2e-auth-server/Dockerfile rename to packages/e2e-auth-server/Dockerfile diff --git a/e2e-auth-server/auth-server.ts b/packages/e2e-auth-server/auth-server.ts similarity index 100% rename from e2e-auth-server/auth-server.ts rename to packages/e2e-auth-server/auth-server.ts diff --git a/e2e-auth-server/package.json b/packages/e2e-auth-server/package.json similarity index 83% rename from e2e-auth-server/package.json rename to packages/e2e-auth-server/package.json index f8ea7243fd..c9df258557 100644 --- a/e2e-auth-server/package.json +++ b/packages/e2e-auth-server/package.json @@ -1,6 +1,7 @@ { "name": "@immich/e2e-auth-server", "version": "0.1.0", + "private": true, "type": "module", "main": "auth-server.ts", "scripts": { @@ -11,5 +12,6 @@ "@types/oidc-provider": "^9.0.0", "oidc-provider": "^9.0.0", "tsx": "^4.20.6" - } + }, + "packageManager": "pnpm@10.33.4" } diff --git a/e2e-auth-server/startup.ts b/packages/e2e-auth-server/startup.ts similarity index 100% rename from e2e-auth-server/startup.ts rename to packages/e2e-auth-server/startup.ts diff --git a/e2e-auth-server/test-keys.ts b/packages/e2e-auth-server/test-keys.ts similarity index 100% rename from e2e-auth-server/test-keys.ts rename to packages/e2e-auth-server/test-keys.ts diff --git a/packages/plugin-core/.gitignore b/packages/plugin-core/.gitignore new file mode 100644 index 0000000000..c925c21d56 --- /dev/null +++ b/packages/plugin-core/.gitignore @@ -0,0 +1,2 @@ +/dist +/node_modules diff --git a/packages/plugin-core/.prettierrc b/packages/plugin-core/.prettierrc new file mode 100644 index 0000000000..7cebf3813c --- /dev/null +++ b/packages/plugin-core/.prettierrc @@ -0,0 +1,6 @@ +{ + "singleQuote": true, + "trailingComma": "all", + "printWidth": 120, + "semi": true +} diff --git a/packages/plugin-core/esbuild.js b/packages/plugin-core/esbuild.js new file mode 100644 index 0000000000..4e1eda47e2 --- /dev/null +++ b/packages/plugin-core/esbuild.js @@ -0,0 +1,11 @@ +const esbuild = require('esbuild'); + +esbuild.build({ + entryPoints: ['src/index.ts'], + outdir: 'dist', + bundle: true, + sourcemap: false, + minify: false, // might want to use true for production build + format: 'cjs', // needs to be CJS for now + target: ['es2020'], // don't go over es2020 because quickjs doesn't support it +}); diff --git a/packages/plugin-core/manifest.json b/packages/plugin-core/manifest.json new file mode 100644 index 0000000000..48b4bee2c8 --- /dev/null +++ b/packages/plugin-core/manifest.json @@ -0,0 +1,333 @@ +{ + "name": "immich-plugin-core", + "version": "2.0.1", + "title": "Immich Core Plugin", + "description": "Core workflow capabilities for Immich", + "author": "Immich Team", + "wasmPath": "dist/plugin.wasm", + "templates": [ + { + "name": "screenshots-smart-album", + "title": "Archive screenshots", + "description": "Archive uploads with \"screenshot\" in the filename and optionally add them to an album", + "trigger": "AssetCreate", + "steps": [ + { + "method": "immich-plugin-core#assetFileFilter", + "config": { + "pattern": "screenshot", + "matchType": "contains", + "caseSensitive": false + } + }, + { + "method": "immich-plugin-core#assetArchive", + "config": { + "inverse": false + } + }, + { + "method": "immich-plugin-core#assetAddToAlbums", + "config": { + "albumName": "Screenshots", + "albumIds": [] + } + } + ], + "uiHints": ["SmartAlbum"] + }, + { + "name": "missing-timezone-smart-album", + "title": "Missing timezone", + "description": "Automatically create an album for assets without a time zone", + "trigger": "AssetMetadataExtraction", + "steps": [ + { + "method": "immich-plugin-core#assetMissingTimeZoneFilter", + "config": {} + }, + { + "method": "immich-plugin-core#assetAddToAlbums", + "config": { + "albumName": "Missing time zone", + "albumIds": [] + } + } + ], + "uiHints": ["SmartAlbum"] + } + ], + "methods": [ + { + "name": "assetFileFilter", + "title": "Filter by filename", + "description": "Filter assets by filename pattern using text matching or regular expressions", + "types": ["AssetV1"], + "schema": { + "type": "object", + "properties": { + "pattern": { + "type": "string", + "title": "Filename pattern", + "description": "Text or regex pattern to match against filename" + }, + "matchType": { + "type": "string", + "title": "Match type", + "enum": ["contains", "startsWith", "exact", "regex"], + "default": "contains", + "description": "Type of pattern matching to perform" + }, + "caseSensitive": { + "type": "boolean", + "default": false, + "title": "Case sensitive", + "description": "Whether matching should be case-sensitive" + } + }, + "required": ["pattern"] + }, + "uiHints": ["Filter"] + }, + { + "name": "assetMissingTimeZoneFilter", + "title": "Filter by missing time zone", + "description": "Filter assets that have no time zone information", + "types": ["AssetV1"], + "schema": { + "type": "object", + "properties": { + "inverse": { + "type": "boolean", + "title": "Inverse", + "description": "Missing by default, set to true to filter assets with a time zone", + "default": false + } + } + }, + "uiHints": ["Filter"] + }, + { + "name": "filterFileType", + "title": "Filter by file type", + "description": "Filter assets by file type", + "types": ["AssetV1"], + "schema": { + "type": "object", + "properties": { + "fileTypes": { + "title": "File types", + "description": "Allowed file types", + "type": "string", + "array": true, + "enum": ["image", "video"] + } + }, + "required": ["fileTypes"] + }, + "uiHints": ["Filter"] + }, + { + "name": "filterPerson", + "title": "Filter by person", + "description": "Filter by detected person", + "types": ["AssetV1"], + "schema": { + "properties": { + "personIds": { + "type": "string", + "array": true, + "title": "Person IDs", + "description": "List of person to match", + "uiHint": "personId" + }, + "matchAny": { + "type": "boolean", + "title": "Match any", + "default": true, + "description": "Match any name (true) or require all names (false)" + } + }, + "required": ["personIds"] + }, + "uiHints": ["Filter"] + }, + { + "name": "assetArchive", + "title": "Archive asset", + "description": "Change asset visibility to archive", + "types": ["AssetV1"], + "schema": { + "properties": { + "inverse": { + "title": "Inverse", + "description": "When true will unarchive any archived assets", + "type": "boolean" + } + } + } + }, + { + "name": "assetLock", + "title": "Move to locked folder", + "description": "Change visibility to locked", + "types": ["AssetV1"] + }, + { + "name": "assetTimeline", + "title": "Move to timeline", + "description": "Change visibility to timeline", + "types": ["AssetV1"] + }, + { + "name": "assetVisibility", + "title": "Update visibility", + "description": "Change visibility to selected option", + "types": ["AssetV1"], + "schema": { + "properties": { + "visibility": { + "title": "Visibility", + "description": "Asset visibility", + "type": "string", + "enum": ["archive", "timeline", "locked"] + } + }, + "required": ["visibility"] + } + }, + { + "name": "assetFavorite", + "title": "Favorite", + "description": "Favorite an asset", + "types": ["AssetV1"], + "schema": { + "type": "object", + "properties": { + "inverse": { + "type": "boolean", + "title": "Inverse", + "description": "Unfavorite by default, set to true to favorite instead", + "default": false + } + } + } + }, + { + "name": "assetAddToAlbums", + "title": "Add to Album(s)", + "description": "Add asset to selected albums", + "types": ["AssetV1"], + "hostFunctions": true, + "schema": { + "type": "object", + "properties": { + "albumIds": { + "type": "string", + "title": "Album IDs", + "array": true, + "description": "Target album IDs", + "uiHint": "AlbumId" + }, + "albumName": { + "type": "string", + "title": "Album name", + "description": "Use an album with this name if one exists, otherwise create a new one" + } + }, + "required": ["albumIds"] + } + }, + { + "name": "noop1", + "title": "DEV: Nested properties", + "description": "Example configuration with nested properties", + "types": ["AssetV1"], + "schema": { + "type": "object", + "properties": { + "number1": { + "type": "number", + "title": "Number 1", + "description": "Basic number" + }, + "number2": { + "type": "number", + "title": "Number 2", + "array": true, + "description": "List of numbers" + }, + "string1": { + "type": "string", + "title": "String 1", + "description": "Basic string" + }, + "string2": { + "type": "string", + "title": "String 2", + "array": true, + "description": "List of strings" + }, + "string3": { + "type": "string", + "title": "String 3", + "enum": ["choice-1", "choice-2"], + "description": "Select from a list" + }, + "nested": { + "type": "object", + "title": "Nested", + "description": "Nested properties for nesting", + "properties": { + "nested1": { + "type": "string", + "title": "Nested 1", + "description": "Nested string" + }, + "nested2": { + "type": "number", + "title": "Nested 2", + "description": "Nested number" + }, + "nested3": { + "type": "object", + "title": "Nested 3", + "description": "Nested again", + "properties": { + "nested4": { + "type": "boolean", + "title": "Nested 4", + "description": "Nested, nested boolean" + } + } + } + } + } + } + } + }, + { + "name": "noop2", + "title": "DEV: Album pickers", + "description": "Example configuration with album pickers", + "types": ["AssetV1"], + "schema": { + "properties": { + "albumId": { + "type": "string", + "title": "Album ID", + "description": "Target album ID", + "uiHint": "AlbumId" + }, + "albumIds": { + "type": "string", + "title": "Album IDs", + "description": "Target album IDs", + "array": true, + "uiHint": "AlbumId" + } + } + } + } + ] +} diff --git a/packages/plugin-core/mise.toml b/packages/plugin-core/mise.toml new file mode 100644 index 0000000000..cb69c36ee7 --- /dev/null +++ b/packages/plugin-core/mise.toml @@ -0,0 +1,6 @@ +[tasks.install] +run = "pnpm install --frozen-lockfile" + +[tasks.build] +depends = ["install"] +run = "pnpm --filter @immich/plugin-sdk --filter @immich/plugin-core build" diff --git a/plugins/package.json b/packages/plugin-core/package.json similarity index 80% rename from plugins/package.json rename to packages/plugin-core/package.json index 2aa1bd599f..26b5124426 100644 --- a/plugins/package.json +++ b/packages/plugin-core/package.json @@ -1,5 +1,5 @@ { - "name": "plugins", + "name": "@immich/plugin-core", "version": "1.0.0", "description": "", "main": "src/index.ts", @@ -13,6 +13,8 @@ "license": "AGPL-3.0", "devDependencies": { "@extism/js-pdk": "^1.0.1", + "@immich/sdk": "workspace:*", + "@immich/plugin-sdk": "workspace:*", "esbuild": "^0.28.0", "typescript": "^6.0.0" } diff --git a/packages/plugin-core/src/index.d.ts b/packages/plugin-core/src/index.d.ts new file mode 100644 index 0000000000..170fa13102 --- /dev/null +++ b/packages/plugin-core/src/index.d.ts @@ -0,0 +1,25 @@ +// keep in sync with plugin-sdk/host-functions.ts'; +declare module 'extism:host' { + interface user { + searchAlbums(ptr: PTR): I64; + createAlbum(ptr: PTR): I64; + addAssetsToAlbum(ptr: PTR): I64; + addAssetsToAlbums(ptr: PTR): I64; + } +} + +// keep in sync with manifest.json +declare module 'main' { + // filters + export function assetFileFilter(): I32; + export function assetMissingTimeZoneFilter(): I32; + + // updates + export function assetFavorite(): I32; + export function assetVisibility(): I32; + export function assetArchive(): I32; + export function assetLock(): I32; + export function assetTimeline(): I32; + export function assetTrash(): I32; + export function assetAddToAlbums(): I32; +} diff --git a/packages/plugin-core/src/index.ts b/packages/plugin-core/src/index.ts new file mode 100644 index 0000000000..bcb05cfa19 --- /dev/null +++ b/packages/plugin-core/src/index.ts @@ -0,0 +1,132 @@ +import { wrapper } from '@immich/plugin-sdk'; +import { AssetVisibility, WorkflowType } from '@immich/sdk'; + +type AssetFileFilterConfig = { + pattern: string; + matchType?: 'contains' | 'exact' | 'regex' | 'startsWith'; + caseSensitive?: boolean; +}; +export const assetFileFilter = () => { + return wrapper(({ data, config }) => { + const { pattern, matchType = 'contains', caseSensitive = false } = config; + + const { asset } = data; + + const fileName = asset.originalFileName || ''; + const searchName = caseSensitive ? fileName : fileName.toLowerCase(); + const searchPattern = caseSensitive ? pattern : pattern.toLowerCase(); + + switch (matchType) { + case 'contains': { + return { workflow: { continue: searchName.includes(searchPattern) } }; + } + + case 'exact': { + return { workflow: { continue: searchName === searchPattern } }; + } + + case 'startsWith': { + return { workflow: { continue: searchName.startsWith(searchPattern) } }; + } + + case 'regex': { + const flags = caseSensitive ? '' : 'i'; + const regex = new RegExp(searchPattern, flags); + return { workflow: { continue: regex.test(fileName) } }; + } + + default: { + return {}; + } + } + }); +}; + +export const assetMissingTimeZoneFilter = () => { + return wrapper(({ config, data }) => { + const hasTimeZone = !!data.asset?.exifInfo?.timeZone; + const needsTimeZone = config.inverse ? true : false; + return { workflow: { continue: hasTimeZone === needsTimeZone } }; + }); +}; + +export const assetFavorite = () => { + return wrapper(({ config, data }) => { + const target = config.inverse ? false : true; + if (target !== data.asset.isFavorite) { + return { + changes: { + asset: { isFavorite: target }, + }, + }; + } + }); +}; + +export const assetVisibility = () => { + return wrapper(({ config }) => ({ + changes: { asset: { visibility: config.visibility } }, + })); +}; + +export const assetArchive = () => { + return wrapper(({ config, data }) => { + if (!config.inverse && data.asset.visibility !== AssetVisibility.Archive) { + return { changes: { asset: { visibility: AssetVisibility.Archive } } }; + } + + if (config.inverse && data.asset.visibility === AssetVisibility.Archive) { + return { changes: { asset: { visibility: AssetVisibility.Timeline } } }; + } + + return {}; + }); +}; + +export const assetLock = () => { + return wrapper(({ config, data }) => { + if (!config.inverse && data.asset.visibility !== AssetVisibility.Locked) { + return { changes: { asset: { visibility: AssetVisibility.Locked } } }; + } + + if (config.inverse && data.asset.visibility === AssetVisibility.Locked) { + return { changes: { asset: { visibility: AssetVisibility.Timeline } } }; + } + + return {}; + }); +}; + +export const assetTrash = () => { + // TODO use trash/untrash host functions + return wrapper(() => ({})); +}; + +export const assetAddToAlbums = () => { + return wrapper(({ config, data, functions }) => { + const assetId = data.asset.id; + + if (config.albumIds.length === 0) { + if (!config.albumName) { + return {}; + } + + const [existing] = functions.searchAlbums({ name: config.albumName }); + if (!existing) { + const created = functions.createAlbum({ albumName: config.albumName, assetIds: [assetId] }); + config.albumIds.push(created.id); + return {}; + } + + config.albumIds.push(existing.id); + } + + if (config.albumIds.length === 1) { + functions.addAssetsToAlbum(config.albumIds[0], [assetId]); + return {}; + } + + functions.addAssetsToAlbums({ albumIds: config.albumIds, assetIds: [assetId] }); + return {}; + }); +}; diff --git a/plugins/tsconfig.json b/packages/plugin-core/tsconfig.json similarity index 68% rename from plugins/tsconfig.json rename to packages/plugin-core/tsconfig.json index a0bc730661..23ab692016 100644 --- a/plugins/tsconfig.json +++ b/packages/plugin-core/tsconfig.json @@ -1,20 +1,24 @@ { "compilerOptions": { - "target": "es2020", // Specify ECMAScript target version - "module": "commonjs", // Specify module code generation - "lib": ["es2020"], // Specify a list of library files to be included in the compilation - "types": ["./src/index.d.ts", "./node_modules/@extism/js-pdk"], // Specify a list of type definition files to be included in the compilation - "strict": true, // Enable all strict type-checking options - "esModuleInterop": true, // Enables compatibility with Babel-style module imports - "skipLibCheck": true, // Skip type checking of declaration files "allowJs": true, // Allow JavaScript files to be compiled + "declaration": true, + "emitDeclarationOnly": true, + "esModuleInterop": true, // Enables compatibility with Babel-style module imports + "lib": ["es2020"], // Specify a list of library files to be included in the compilation + "module": "nodenext", // Specify module code generation + "moduleResolution": "nodenext", + "noEmit": true, // Do not emit outputs (no .js or .d.ts files) + "outDir": "./dist", "rootDir": "./src", - "noEmit": true // Do not emit outputs (no .js or .d.ts files) + "skipLibCheck": true, // Skip type checking of declaration files + "strict": true, // Enable all strict type-checking options + "target": "es2020", // Specify ECMAScript target version + "types": ["./src/index.d.ts", "./node_modules/@extism/js-pdk"] // Specify a list of type definition files to be included in the compilation }, - "include": [ - "src/**/*.ts" // Include all TypeScript files in src directory - ], "exclude": [ "node_modules" // Exclude the node_modules directory + ], + "include": [ + "src/**/*.ts" // Include all TypeScript files in src directory ] } diff --git a/packages/plugin-sdk/.gitignore b/packages/plugin-sdk/.gitignore new file mode 100644 index 0000000000..c925c21d56 --- /dev/null +++ b/packages/plugin-sdk/.gitignore @@ -0,0 +1,2 @@ +/dist +/node_modules diff --git a/packages/plugin-sdk/esbuild.js b/packages/plugin-sdk/esbuild.js new file mode 100644 index 0000000000..d2e036e5c7 --- /dev/null +++ b/packages/plugin-sdk/esbuild.js @@ -0,0 +1,11 @@ +import esbuild from 'esbuild'; + +esbuild.build({ + entryPoints: ['src/index.ts'], + outdir: 'dist', + bundle: true, + sourcemap: false, + minify: false, + format: 'esm', + target: ['es2020'], +}); diff --git a/packages/plugin-sdk/package.json b/packages/plugin-sdk/package.json new file mode 100644 index 0000000000..7c44368fbc --- /dev/null +++ b/packages/plugin-sdk/package.json @@ -0,0 +1,39 @@ +{ + "name": "@immich/plugin-sdk", + "version": "0.0.0", + "description": "", + "type": "module", + "exports": { + "./host-functions": { + "import": "./dist/host-functions.js", + "types": "./dist/host-functions.d.ts" + }, + ".": { + "import": "./dist/index.js", + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "build": "node esbuild.js && tsc --emitDeclarationOnly && tsc-alias" + }, + "files": [ + "dist" + ], + "keywords": [], + "author": "", + "license": "GNU Affero General Public License version 3", + "packageManager": "pnpm@10.33.4", + "devDependencies": { + "@extism/js-pdk": "^1.1.1", + "@immich/sdk": "workspace:*", + "@types/node": "^24.12.4", + "esbuild": "^0.28.0", + "tsc-alias": "^1.8.16", + "typescript": "^5.9.3" + }, + "peerDependencies": { + "@extism/js-pdk": "^1.1.1" + } +} diff --git a/packages/plugin-sdk/src/host-functions.ts b/packages/plugin-sdk/src/host-functions.ts new file mode 100644 index 0000000000..281e27c83c --- /dev/null +++ b/packages/plugin-sdk/src/host-functions.ts @@ -0,0 +1,79 @@ +import { + getAllAlbums, + type AlbumResponseDto, + type BulkIdResponseDto, + type BulkIdsDto, + type CreateAlbumDto, +} from '@immich/sdk'; + +// keep in sync with plugin-core/src/index.d.ts'; +declare module 'extism:host' { + interface user { + searchAlbums(ptr: PTR): I64; + createAlbum(ptr: PTR): I64; + addAssetsToAlbum(ptr: PTR): I64; + addAssetsToAlbums(ptr: PTR): I64; + } +} + +type AlbumsToAssets = { + assetIds: string[]; + albumIds: string[]; +}; + +type HostFunctionSuccessResult = { success: true; response: T }; +type HostFunctionErrorResult = { + success: false; + status: number; + message: string; +}; +type HostFunctionResult = + | HostFunctionSuccessResult + | HostFunctionErrorResult; + +type QueryParams any> = Parameters[0]; +type AlbumSearchDto = QueryParams; + +export const hostFunctions = (authToken: string) => { + const host = Host.getFunctions(); + type HostFunctionName = keyof typeof host; + + const call = (name: HostFunctionName, authToken: string, args: T) => { + const pointer1 = Memory.fromString(JSON.stringify({ authToken, args })); + const fn = host[name]; + const handler = Memory.find(fn(pointer1.offset)); + + try { + const result = JSON.parse(handler.readString()) as HostFunctionResult; + + if (result.success) { + return result.response; + } + + throw new Error( + `Failed to call host function "${String(name)}", received ${result.status} - ${JSON.stringify(result.message)}`, + ); + } finally { + handler.free(); + pointer1.free(); + } + }; + + return { + // album + searchAlbums: (dto: AlbumSearchDto) => + call<[AlbumSearchDto], AlbumResponseDto[]>('searchAlbums', authToken, [ + dto, + ]), + createAlbum: (dto: CreateAlbumDto) => + call<[CreateAlbumDto], AlbumResponseDto>('createAlbum', authToken, [dto]), + addAssetsToAlbum: (albumId: string, assetIds: string[]) => + call<[string, BulkIdsDto], BulkIdResponseDto[]>( + 'addAssetsToAlbum', + authToken, + [albumId, { ids: assetIds }], + ), + addAssetsToAlbums: ({ assetIds, albumIds }: AlbumsToAssets) => + call('addAssetsToAlbums', authToken, [{ albumIds, assetIds }]), + }; +}; diff --git a/packages/plugin-sdk/src/index.ts b/packages/plugin-sdk/src/index.ts new file mode 100644 index 0000000000..c83369410a --- /dev/null +++ b/packages/plugin-sdk/src/index.ts @@ -0,0 +1,3 @@ +export * from 'src/host-functions.js'; +export * from 'src/sdk.js'; +export * from 'src/types.js'; diff --git a/packages/plugin-sdk/src/sdk.ts b/packages/plugin-sdk/src/sdk.ts new file mode 100644 index 0000000000..283e9c3dd5 --- /dev/null +++ b/packages/plugin-sdk/src/sdk.ts @@ -0,0 +1,53 @@ +import type { WorkflowType } from '@immich/sdk'; +import { hostFunctions } from 'src/host-functions.js'; +import type { + ConfigValue, + WorkflowEventPayload, + WorkflowResponse, + WorkflowStepConfig, +} from 'src/types.js'; + +export const wrapper = < + T extends WorkflowType = WorkflowType, + TConfig extends ConfigValue = ConfigValue, +>( + fn: ( + payload: WorkflowEventPayload & { + functions: ReturnType; + }, + ) => WorkflowResponse | undefined, +) => { + const input = Host.inputString(); + + try { + const payload = JSON.parse(input) as WorkflowEventPayload; + const event = { + ...payload, + functions: hostFunctions(payload.workflow.authToken), + }; + + const eventConfigBefore = JSON.stringify(event.config); + + console.debug( + `Inputs: trigger=${event.trigger}, event=${event.type}, config=${eventConfigBefore}`, + ); + + const response = fn(event) ?? {}; + + // if config changed, notify host + const eventConfigAfter = JSON.stringify(event.config); + if (!response.config && eventConfigBefore !== eventConfigAfter) { + response.config = event.config as WorkflowStepConfig; + } + + console.debug( + `Outputs: workflow=${JSON.stringify(response.workflow)}, changes=${JSON.stringify(response.changes)}, data=${JSON.stringify(response.data)}, config=${JSON.stringify(response.config)}`, + ); + + const output = JSON.stringify(response); + Host.outputString(output); + } catch (error: Error | any) { + console.error(`Unhandled plugin exception: ${error.message || error}`); + throw error; + } +}; diff --git a/packages/plugin-sdk/src/types.ts b/packages/plugin-sdk/src/types.ts new file mode 100644 index 0000000000..67c179f4a6 --- /dev/null +++ b/packages/plugin-sdk/src/types.ts @@ -0,0 +1,130 @@ +import type { AssetTypeEnum, AssetVisibility, WorkflowType } from '@immich/sdk'; + +type DeepPartial = T extends Date + ? T + : T extends Record + ? { [K in keyof T]?: DeepPartial } + : T extends Array + ? DeepPartial[] + : T; + +export type WorkflowEventMap = { + [WorkflowType.AssetV1]: AssetV1; + [WorkflowType.AssetPersonV1]: AssetPersonV1; +}; + +export type WorkflowEventData = WorkflowEventMap[T]; + +export enum WorkflowTrigger { + AssetCreate = 'AssetCreate', + AssetMetadataExtraction = 'AssetMetadataExtraction', + PersonRecognized = 'PersonRecognized', +} + +export type WorkflowEventPayload< + T extends WorkflowType = WorkflowType, + TConfig = WorkflowStepConfig, +> = { + trigger: WorkflowTrigger; + type: T; + data: WorkflowEventData; + config: TConfig; + workflow: { + id: string; + authToken: string; + stepId: string; + debug?: boolean; + }; +}; + +export type WorkflowChanges = + DeepPartial>; + +export type WorkflowResponse = { + workflow?: { + /** stop the workflow */ + continue?: boolean; + }; + changes?: WorkflowChanges; + /** data to be passed to the next workflow step */ + data?: Record; + /** update step config */ + config?: WorkflowStepConfig; +}; + +export type WorkflowStepConfig = { + [key: string]: ConfigValue; +}; + +export type ConfigValue = + | string + | number + | boolean + | null + | ConfigValue[] + | { [key: string]: ConfigValue }; + +export type AssetV1 = { + asset: { + id: string; + ownerId: string; + type: AssetTypeEnum; + originalPath: string; + fileCreatedAt: string; + fileModifiedAt: string; + isFavorite: boolean; + checksum: Buffer; // sha1 checksum + livePhotoVideoId: string | null; + updatedAt: string; + createdAt: string; + originalFileName: string; + isOffline: boolean; + libraryId: string | null; + isExternal: boolean; + deletedAt: string | null; + localDateTime: string; + stackId: string | null; + duplicateId: string | null; + visibility: AssetVisibility; + isEdited: boolean; + exifInfo: { + make: string | null; + model: string | null; + exifImageWidth: number | null; + exifImageHeight: number | null; + fileSizeInByte: number | null; + orientation: string | null; + dateTimeOriginal: string | null; + modifyDate: string | null; + lensModel: string | null; + fNumber: number | null; + focalLength: number | null; + iso: number | null; + latitude: number | null; + longitude: number | null; + city: string | null; + state: string | null; + country: string | null; + description: string | null; + fps: number | null; + exposureTime: string | null; + livePhotoCID: string | null; + timeZone: string | null; + projectionType: string | null; + profileDescription: string | null; + colorspace: string | null; + bitsPerSample: number | null; + autoStackId: string | null; + rating: number | null; + tags: string[] | null; + updatedAt: string | null; + } | null; + }; +}; + +export type AssetPersonV1 = AssetV1 & { + person: { + id: string; + name: string; + }; +}; diff --git a/packages/plugin-sdk/tsconfig.json b/packages/plugin-sdk/tsconfig.json new file mode 100644 index 0000000000..2dbd91de0a --- /dev/null +++ b/packages/plugin-sdk/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "declaration": true, + "emitDeclarationOnly": true, + "esModuleInterop": true, + "exactOptionalPropertyTypes": true, + "isolatedModules": true, + "lib": ["esnext"], + "module": "nodenext", + "moduleDetection": "force", + "noUncheckedIndexedAccess": true, + "noUncheckedSideEffectImports": true, + "outDir": "./dist", + "paths": { + "src/*": ["./src/*"] + }, + "removeComments": true, + "rootDir": "./src", + "skipLibCheck": true, + "sourceMap": false, + "strict": true, + "target": "esnext", + "types": ["node", "@extism/js-pdk"], + "verbatimModuleSyntax": true + } +} diff --git a/open-api/typescript-sdk/.npmignore b/packages/sdk/.npmignore similarity index 100% rename from open-api/typescript-sdk/.npmignore rename to packages/sdk/.npmignore diff --git a/open-api/typescript-sdk/README.md b/packages/sdk/README.md similarity index 100% rename from open-api/typescript-sdk/README.md rename to packages/sdk/README.md diff --git a/open-api/typescript-sdk/package.json b/packages/sdk/package.json similarity index 84% rename from open-api/typescript-sdk/package.json rename to packages/sdk/package.json index 7e212bcfbf..2ef3fac60c 100644 --- a/open-api/typescript-sdk/package.json +++ b/packages/sdk/package.json @@ -2,6 +2,11 @@ "name": "@immich/sdk", "version": "2.7.5", "description": "Auto-generated TypeScript SDK for the Immich API", + "repository": { + "type": "git", + "url": "git+https://github.com/immich-app/immich.git", + "directory": "packages/sdk" + }, "type": "module", "main": "./build/index.js", "types": "./build/index.d.ts", @@ -19,15 +24,7 @@ "@oazapfts/runtime": "^1.0.2" }, "devDependencies": { - "@types/node": "^24.12.2", + "@types/node": "^24.12.4", "typescript": "^6.0.0" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/immich-app/immich.git", - "directory": "open-api/typescript-sdk" - }, - "volta": { - "node": "24.15.0" } } diff --git a/open-api/typescript-sdk/src/fetch-client.ts b/packages/sdk/src/fetch-client.ts similarity index 95% rename from open-api/typescript-sdk/src/fetch-client.ts rename to packages/sdk/src/fetch-client.ts index 27511d2522..489593190e 100644 --- a/open-api/typescript-sdk/src/fetch-client.ts +++ b/packages/sdk/src/fetch-client.ts @@ -1,6 +1,6 @@ /** * Immich - * 2.7.5 + * 3.0.0 * DO NOT MODIFY - This file has been generated using oazapfts. * See https://www.npmjs.com/package/oazapfts */ @@ -298,6 +298,8 @@ export type MemoriesResponse = { export type PeopleResponse = { /** Whether people are enabled */ enabled: boolean; + /** People face threshold */ + minimumFaces?: number; /** Whether people appear in web sidebar */ sidebarWeb: boolean; }; @@ -375,6 +377,8 @@ export type MemoriesUpdate = { export type PeopleUpdate = { /** Whether people are enabled */ enabled?: boolean; + /** People face threshold */ + minimumFaces?: number; /** Whether people appear in web sidebar */ sidebarWeb?: boolean; }; @@ -787,29 +791,11 @@ export type ExifResponseDto = { /** Time zone */ timeZone?: string | null; }; -export type AssetFaceWithoutPersonResponseDto = { - /** Bounding box X1 coordinate */ - boundingBoxX1: number; - /** Bounding box X2 coordinate */ - boundingBoxX2: number; - /** Bounding box Y1 coordinate */ - boundingBoxY1: number; - /** Bounding box Y2 coordinate */ - boundingBoxY2: number; - /** Face ID */ - id: string; - /** Image height in pixels */ - imageHeight: number; - /** Image width in pixels */ - imageWidth: number; - sourceType?: SourceType; -}; -export type PersonWithFacesResponseDto = { +export type PersonResponseDto = { /** Person date of birth */ birthDate: string | null; /** Person color (hex) */ color?: string; - faces: AssetFaceWithoutPersonResponseDto[]; /** Person ID */ id: string; /** Is favorite */ @@ -892,7 +878,7 @@ export type AssetResponseDto = { owner?: UserResponseDto; /** Owner user ID */ ownerId: string; - people?: PersonWithFacesResponseDto[]; + people?: PersonResponseDto[]; /** Is resized */ resized?: boolean; stack?: (AssetStackResponseDto) | null; @@ -900,7 +886,6 @@ export type AssetResponseDto = { /** Thumbhash for thumbnail generation (base64) also used as the c query param for thumbnail cache busting. */ thumbhash: string | null; "type": AssetTypeEnum; - unassignedFaces?: AssetFaceWithoutPersonResponseDto[]; /** The UTC timestamp when the asset record was last updated in the database. This is automatically maintained by the database and reflects when any field in the asset was last modified. */ updatedAt: string; visibility: AssetVisibility; @@ -1136,24 +1121,6 @@ export type DuplicateResolveDto = { /** List of duplicate groups to resolve */ groups: DuplicateResolveGroupDto[]; }; -export type PersonResponseDto = { - /** Person date of birth */ - birthDate: string | null; - /** Person color (hex) */ - color?: string; - /** Person ID */ - id: string; - /** Is favorite */ - isFavorite?: boolean; - /** Is hidden */ - isHidden: boolean; - /** Person name */ - name: string; - /** Thumbnail path */ - thumbnailPath: string; - /** Last update date */ - updatedAt?: string; -}; export type AssetFaceResponseDto = { /** Bounding box X1 coordinate */ boundingBoxX1: number; @@ -1515,72 +1482,33 @@ export type PersonStatisticsResponseDto = { /** Number of assets */ assets: number; }; -export type PluginJsonSchemaProperty = { - additionalProperties?: boolean | PluginJsonSchemaProperty; - "default"?: any; - description?: string; - "enum"?: string[]; - items?: PluginJsonSchemaProperty; - properties?: { - [key: string]: PluginJsonSchemaProperty; - }; - required?: string[]; - "type"?: PluginJsonSchemaType; -}; -export type PluginJsonSchema = { - additionalProperties?: boolean; - description?: string; - properties?: { - [key: string]: PluginJsonSchemaProperty; - }; - required?: string[]; - "type"?: PluginJsonSchemaType; -}; -export type PluginActionResponseDto = { - /** Action description */ +export type PluginMethodResponseDto = { + /** Description */ description: string; - /** Action ID */ - id: string; - /** Method name */ - methodName: string; - /** Plugin ID */ - pluginId: string; - /** Action schema */ - schema: (PluginJsonSchema) | null; - /** Supported contexts */ - supportedContexts: PluginContextType[]; - /** Action title */ - title: string; -}; -export type PluginFilterResponseDto = { - /** Filter description */ - description: string; - /** Filter ID */ - id: string; - /** Method name */ - methodName: string; - /** Plugin ID */ - pluginId: string; - /** Filter schema */ - schema: (PluginJsonSchema) | null; - /** Supported contexts */ - supportedContexts: PluginContextType[]; - /** Filter title */ + hostFunctions: boolean; + /** Key */ + key: string; + /** Name */ + name: string; + schema?: {}; + /** Title */ title: string; + /** Workflow types */ + types: WorkflowType[]; + /** Ui hints */ + uiHints: string[]; }; export type PluginResponseDto = { - /** Plugin actions */ - actions: PluginActionResponseDto[]; /** Plugin author */ author: string; /** Creation date */ createdAt: string; /** Plugin description */ description: string; - /** Plugin filters */ - filters: PluginFilterResponseDto[]; /** Plugin ID */ id: string; + /** Plugin methods */ + methods: PluginMethodResponseDto[]; /** Plugin name */ name: string; /** Plugin title */ @@ -1590,9 +1518,29 @@ export type PluginResponseDto = { /** Plugin version */ version: string; }; -export type PluginTriggerResponseDto = { - contextType: PluginContextType; - "type": PluginTriggerType; +export type PluginTemplateStepResponseDto = { + /** Step configuration */ + config: { + [key: string]: any; + } | null; + /** Whether the step is enabled */ + enabled?: boolean; + /** Step plugin method */ + method: string; +}; +export type PluginTemplateResponseDto = { + /** Template description */ + description: string; + /** Template key (unique across all templates) */ + key: string; + /** Workflow steps */ + steps: PluginTemplateStepResponseDto[]; + /** Template title */ + title: string; + /** Workflow trigger */ + trigger: WorkflowTrigger; + /** Ui hints, for example "smart-album" */ + uiHints: string[]; }; export type QueueResponseDto = { /** Whether the queue is paused */ @@ -2019,6 +1967,8 @@ export type ServerConfigDto = { mapDarkStyleUrl: string; /** Map light style URL */ mapLightStyleUrl: string; + /** People min faces server default */ + minFaces: number; /** OAuth button text */ oauthButtonText: string; /** Whether public user registration is enabled */ @@ -2049,6 +1999,8 @@ export type ServerFeaturesDto = { ocr: boolean; /** Whether password login is enabled */ passwordLogin: boolean; + /** Whether real-time transcoding is enabled */ + realtimeTranscoding: boolean; /** Whether reverse geocoding is enabled */ reverseGeocoding: boolean; /** Whether search is enabled */ @@ -2132,6 +2084,8 @@ export type ServerVersionResponseDto = { minor: number; /** Patch version number */ patch: number; + /** Pre-release version number */ + prerelease: number | null; }; export type VersionCheckStateResponseDto = { /** Last check timestamp */ @@ -2238,8 +2192,6 @@ export type SharedLinkEditDto = { allowDownload?: boolean; /** Allow uploads */ allowUpload?: boolean; - /** Whether to change the expiry time. Few clients cannot send null to set the expiryTime to never. Setting this flag and not sending expiryAt is considered as null instead. Clients that can send null values can ignore this. */ - changeExpiryTime?: boolean; /** Link description */ description?: string | null; /** Expiration date */ @@ -2307,6 +2259,10 @@ export type DatabaseBackupConfig = { export type SystemConfigBackupsDto = { database: DatabaseBackupConfig; }; +export type SystemConfigFFmpegRealtimeDto = { + /** Enable real-time HLS transcoding (alpha) */ + enabled: boolean; +}; export type SystemConfigFFmpegDto = { accel: TranscodeHWAccel; /** Accelerated decode */ @@ -2330,6 +2286,7 @@ export type SystemConfigFFmpegDto = { preferredHwDevice: string; /** Preset */ preset: string; + realtime: SystemConfigFFmpegRealtimeDto; /** References */ refs: number; targetAudioCodec: AudioCodec; @@ -2479,6 +2436,7 @@ export type SystemConfigMetadataDto = { faces: SystemConfigFacesDto; }; export type SystemConfigNewVersionCheckDto = { + channel: ReleaseChannel; /** Enabled */ enabled: boolean; }; @@ -2670,9 +2628,11 @@ export type TagUpdateDto = { }; export type TimeBucketAssetResponseDto = { /** Array of city names extracted from EXIF GPS data */ - city: (string | null)[]; + city?: (string | null)[]; /** Array of country names extracted from EXIF GPS data */ - country: (string | null)[]; + country?: (string | null)[]; + /** Array of UTC timestamps when each asset was originally uploaded to Immich */ + createdAt: string[]; /** Array of video/gif durations in milliseconds (null for static images) */ duration: (number | null)[]; /** Array of file creation timestamps in UTC */ @@ -2745,91 +2705,93 @@ export type CreateProfileImageResponseDto = { /** User ID */ userId: string; }; -export type PluginConfigValue = any; -export type WorkflowActionConfig = { - [key: string]: PluginConfigValue; -}; -export type WorkflowActionResponseDto = { - actionConfig: (WorkflowActionConfig) | null; - /** Action ID */ - id: string; - /** Action order */ - order: number; - /** Plugin action ID */ - pluginActionId: string; - /** Workflow ID */ - workflowId: string; -}; -export type WorkflowFilterConfig = { - [key: string]: PluginConfigValue; -}; -export type WorkflowFilterResponseDto = { - filterConfig: (WorkflowFilterConfig) | null; - /** Filter ID */ - id: string; - /** Filter order */ - order: number; - /** Plugin filter ID */ - pluginFilterId: string; - /** Workflow ID */ - workflowId: string; +export type WorkflowStepDto = { + /** Step configuration */ + config: { + [key: string]: any; + } | null; + /** Step is enabled */ + enabled?: boolean; + /** Step plugin method */ + method: string; }; export type WorkflowResponseDto = { - /** Workflow actions */ - actions: WorkflowActionResponseDto[]; /** Creation date */ createdAt: string; /** Workflow description */ - description: string; + description: string | null; /** Workflow enabled */ enabled: boolean; - /** Workflow filters */ - filters: WorkflowFilterResponseDto[]; /** Workflow ID */ id: string; /** Workflow name */ name: string | null; - /** Owner user ID */ - ownerId: string; - triggerType: PluginTriggerType; -}; -export type WorkflowActionItemDto = { - actionConfig?: WorkflowActionConfig; - /** Plugin action ID */ - pluginActionId: string; -}; -export type WorkflowFilterItemDto = { - filterConfig?: WorkflowFilterConfig; - /** Plugin filter ID */ - pluginFilterId: string; + /** Workflow steps */ + steps: WorkflowStepDto[]; + /** Workflow trigger type */ + trigger: WorkflowTrigger; + /** Update date */ + updatedAt: string; }; export type WorkflowCreateDto = { - /** Workflow actions */ - actions: WorkflowActionItemDto[]; /** Workflow description */ - description?: string; + description?: string | null; /** Workflow enabled */ enabled?: boolean; - /** Workflow filters */ - filters: WorkflowFilterItemDto[]; /** Workflow name */ - name: string; - triggerType: PluginTriggerType; + name?: string | null; + steps?: WorkflowStepDto[]; + /** Workflow trigger type */ + trigger: WorkflowTrigger; +}; +export type WorkflowTriggerResponseDto = { + /** Trigger type */ + trigger: WorkflowTrigger; + /** Workflow types */ + types: WorkflowType[]; }; export type WorkflowUpdateDto = { - /** Workflow actions */ - actions?: WorkflowActionItemDto[]; /** Workflow description */ - description?: string; + description?: string | null; /** Workflow enabled */ enabled?: boolean; - /** Workflow filters */ - filters?: WorkflowFilterItemDto[]; /** Workflow name */ - name?: string; - triggerType?: PluginTriggerType; + name?: string | null; + steps?: WorkflowStepDto[]; + /** Workflow trigger type */ + trigger?: WorkflowTrigger; +}; +export type WorkflowShareStepDto = { + /** Step configuration */ + config: { + [key: string]: any; + } | null; + /** Step is enabled */ + enabled?: boolean; + /** Step plugin method */ + method: string; +}; +export type WorkflowShareResponseDto = { + /** Workflow description */ + description: string | null; + /** Workflow name */ + name: string | null; + /** Workflow steps */ + steps: WorkflowShareStepDto[]; + /** Workflow trigger type */ + trigger: WorkflowTrigger; }; export type LicenseResponseDto = UserLicense; +export type ReleaseEventV1 = { + /** When the server last checked for a latest version. As an ISO timestamp */ + checkedAt: string; + /** Whether a new version is available */ + isAvailable: boolean; + releaseVersion: ServerVersionResponseDto; + serverVersion: ServerVersionResponseDto; + /** Release type */ + "type": ReleaseType; +}; export type SyncAckV1 = {}; export type SyncAlbumDeleteV1 = { /** Album ID */ @@ -3078,6 +3040,8 @@ export type SyncAssetOcrV1 = { export type SyncAssetV1 = { /** Checksum */ checksum: string; + /** Uploaded to Immich at */ + createdAt: string | null; /** Deleted at */ deletedAt: string | null; /** Duration */ @@ -3116,6 +3080,8 @@ export type SyncAssetV1 = { export type SyncAssetV2 = { /** Checksum */ checksum: string; + /** Uploaded to Immich at */ + createdAt: string | null; /** Deleted at */ deletedAt: string | null; /** Duration */ @@ -3695,16 +3661,22 @@ export function getUserStatisticsAdmin({ id, isFavorite, isTrashed, visibility } /** * List all albums */ -export function getAllAlbums({ assetId, shared }: { +export function getAllAlbums({ assetId, id, isOwned, isShared, name }: { assetId?: string; - shared?: boolean; + id?: string; + isOwned?: boolean; + isShared?: boolean; + name?: string; }, opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ status: 200; data: AlbumResponseDto[]; }>(`/albums${QS.query(QS.explode({ assetId, - shared + id, + isOwned, + isShared, + name }))}`, { ...opts })); @@ -4302,6 +4274,82 @@ export function playAssetVideo({ id, key, slug }: { ...opts })); } +/** + * Get HLS main playlist + */ +export function getMainPlaylist({ id, key, slug }: { + id: string; + key?: string; + slug?: string; +}, opts?: Oazapfts.RequestOpts) { + return oazapfts.ok(oazapfts.fetchBlob<{ + status: 200; + data: string; + }>(`/assets/${encodeURIComponent(id)}/video/stream/main.m3u8${QS.query(QS.explode({ + key, + slug + }))}`, { + ...opts + })); +} +/** + * End HLS streaming session + */ +export function endSession({ id, key, sessionId, slug }: { + id: string; + key?: string; + sessionId: string; + slug?: string; +}, opts?: Oazapfts.RequestOpts) { + return oazapfts.ok(oazapfts.fetchText(`/assets/${encodeURIComponent(id)}/video/stream/${encodeURIComponent(sessionId)}${QS.query(QS.explode({ + key, + slug + }))}`, { + ...opts, + method: "DELETE" + })); +} +/** + * Get HLS media playlist + */ +export function getMediaPlaylist({ id, key, sessionId, slug, variantIndex }: { + id: string; + key?: string; + sessionId: string; + slug?: string; + variantIndex: number; +}, opts?: Oazapfts.RequestOpts) { + return oazapfts.ok(oazapfts.fetchBlob<{ + status: 200; + data: string; + }>(`/assets/${encodeURIComponent(id)}/video/stream/${encodeURIComponent(sessionId)}/${encodeURIComponent(variantIndex)}/playlist.m3u8${QS.query(QS.explode({ + key, + slug + }))}`, { + ...opts + })); +} +/** + * Get HLS segment or init file + */ +export function getSegment({ filename, id, key, sessionId, slug, variantIndex }: { + filename: string; + id: string; + key?: string; + sessionId: string; + slug?: string; + variantIndex: number; +}, opts?: Oazapfts.RequestOpts) { + return oazapfts.ok(oazapfts.fetchBlob<{ + status: 200; + data: Blob; + }>(`/assets/${encodeURIComponent(id)}/video/stream/${encodeURIComponent(sessionId)}/${encodeURIComponent(variantIndex)}/${encodeURIComponent(filename)}${QS.query(QS.explode({ + key, + slug + }))}`, { + ...opts + })); +} /** * Register admin */ @@ -4518,7 +4566,7 @@ export function resolveDuplicates({ duplicateResolveDto }: { }))); } /** - * Delete a duplicate + * Dismiss a duplicate group */ export function deleteDuplicate({ id }: { id: string; @@ -5307,22 +5355,67 @@ export function getPersonThumbnail({ id }: { /** * List all plugins */ -export function getPlugins(opts?: Oazapfts.RequestOpts) { +export function searchPlugins({ description, enabled, id, name, title, version }: { + description?: string; + enabled?: boolean; + id?: string; + name?: string; + title?: string; + version?: string; +}, opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ status: 200; data: PluginResponseDto[]; - }>("/plugins", { + }>(`/plugins${QS.query(QS.explode({ + description, + enabled, + id, + name, + title, + version + }))}`, { ...opts })); } /** - * List all plugin triggers + * Retrieve plugin methods */ -export function getPluginTriggers(opts?: Oazapfts.RequestOpts) { +export function searchPluginMethods({ description, enabled, id, name, pluginName, pluginVersion, title, trigger, $type }: { + description?: string; + enabled?: boolean; + id?: string; + name?: string; + pluginName?: string; + pluginVersion?: string; + title?: string; + trigger?: WorkflowTrigger; + $type?: WorkflowType; +}, opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ status: 200; - data: PluginTriggerResponseDto[]; - }>("/plugins/triggers", { + data: PluginMethodResponseDto[]; + }>(`/plugins/methods${QS.query(QS.explode({ + description, + enabled, + id, + name, + pluginName, + pluginVersion, + title, + trigger, + "type": $type + }))}`, { + ...opts + })); +} +/** + * Retrieve workflow templates + */ +export function searchPluginTemplates(opts?: Oazapfts.RequestOpts) { + return oazapfts.ok(oazapfts.fetchJson<{ + status: 200; + data: PluginTemplateResponseDto[]; + }>("/plugins/templates", { ...opts })); } @@ -6362,13 +6455,14 @@ export function tagAssets({ id, bulkIdsDto }: { /** * Get time bucket */ -export function getTimeBucket({ albumId, bbox, isFavorite, isTrashed, key, order, personId, slug, tagId, timeBucket, userId, visibility, withCoordinates, withPartners, withStacked }: { +export function getTimeBucket({ albumId, bbox, isFavorite, isTrashed, key, order, orderBy, personId, slug, tagId, timeBucket, userId, visibility, withCoordinates, withPartners, withStacked }: { albumId?: string; bbox?: string; isFavorite?: boolean; isTrashed?: boolean; key?: string; order?: AssetOrder; + orderBy?: AssetOrderBy; personId?: string; slug?: string; tagId?: string; @@ -6389,6 +6483,7 @@ export function getTimeBucket({ albumId, bbox, isFavorite, isTrashed, key, order isTrashed, key, order, + orderBy, personId, slug, tagId, @@ -6405,13 +6500,14 @@ export function getTimeBucket({ albumId, bbox, isFavorite, isTrashed, key, order /** * Get time buckets */ -export function getTimeBuckets({ albumId, bbox, isFavorite, isTrashed, key, order, personId, slug, tagId, userId, visibility, withCoordinates, withPartners, withStacked }: { +export function getTimeBuckets({ albumId, bbox, isFavorite, isTrashed, key, order, orderBy, personId, slug, tagId, userId, visibility, withCoordinates, withPartners, withStacked }: { albumId?: string; bbox?: string; isFavorite?: boolean; isTrashed?: boolean; key?: string; order?: AssetOrder; + orderBy?: AssetOrderBy; personId?: string; slug?: string; tagId?: string; @@ -6431,6 +6527,7 @@ export function getTimeBuckets({ albumId, bbox, isFavorite, isTrashed, key, orde isTrashed, key, order, + orderBy, personId, slug, tagId, @@ -6694,11 +6791,23 @@ export function getUniqueOriginalPaths(opts?: Oazapfts.RequestOpts) { /** * List all workflows */ -export function getWorkflows(opts?: Oazapfts.RequestOpts) { +export function searchWorkflows({ description, enabled, id, name, trigger }: { + description?: string; + enabled?: boolean; + id?: string; + name?: string; + trigger?: WorkflowTrigger; +}, opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ status: 200; data: WorkflowResponseDto[]; - }>("/workflows", { + }>(`/workflows${QS.query(QS.explode({ + description, + enabled, + id, + name, + trigger + }))}`, { ...opts })); } @@ -6717,6 +6826,17 @@ export function createWorkflow({ workflowCreateDto }: { body: workflowCreateDto }))); } +/** + * List all workflow triggers + */ +export function getWorkflowTriggers(opts?: Oazapfts.RequestOpts) { + return oazapfts.ok(oazapfts.fetchJson<{ + status: 200; + data: WorkflowTriggerResponseDto[]; + }>("/workflows/triggers", { + ...opts + })); +} /** * Delete a workflow */ @@ -6757,6 +6877,19 @@ export function updateWorkflow({ id, workflowUpdateDto }: { body: workflowUpdateDto }))); } +/** + * Retrieve a workflow + */ +export function getWorkflowForShare({ id }: { + id: string; +}, opts?: Oazapfts.RequestOpts) { + return oazapfts.ok(oazapfts.fetchJson<{ + status: 200; + data: WorkflowShareResponseDto; + }>(`/workflows/${encodeURIComponent(id)}/share`, { + ...opts + })); +} export enum ReactionLevel { Album = "album", Asset = "asset" @@ -7007,11 +7140,6 @@ export enum AssetJobName { RegenerateThumbnail = "regenerate-thumbnail", TranscodeVideo = "transcode-video" } -export enum SourceType { - MachineLearning = "machine-learning", - Exif = "exif", - Manual = "manual" -} export enum AssetTypeEnum { Image = "IMAGE", Video = "VIDEO", @@ -7033,6 +7161,11 @@ export enum AssetMediaSize { Preview = "preview", Thumbnail = "thumbnail" } +export enum SourceType { + MachineLearning = "machine-learning", + Exif = "exif", + Manual = "manual" +} export enum ManualJobName { PersonCleanup = "person-cleanup", TagCleanup = "tag-cleanup", @@ -7080,22 +7213,13 @@ export enum PartnerDirection { SharedBy = "shared-by", SharedWith = "shared-with" } -export enum PluginJsonSchemaType { - String = "string", - Number = "number", - Integer = "integer", - Boolean = "boolean", - Object = "object", - Array = "array", - Null = "null" +export enum WorkflowType { + AssetV1 = "AssetV1", + AssetPersonV1 = "AssetPersonV1" } -export enum PluginContextType { - Asset = "asset", - Album = "album", - Person = "person" -} -export enum PluginTriggerType { +export enum WorkflowTrigger { AssetCreate = "AssetCreate", + AssetMetadataExtraction = "AssetMetadataExtraction", PersonRecognized = "PersonRecognized" } export enum QueueJobStatus { @@ -7136,6 +7260,7 @@ export enum JobName { LibrarySyncFilesQueueAll = "LibrarySyncFilesQueueAll", LibrarySyncFiles = "LibrarySyncFiles", LibraryScanQueueAll = "LibraryScanQueueAll", + HlsSessionCleanup = "HlsSessionCleanup", MemoryCleanup = "MemoryCleanup", MemoryGenerate = "MemoryGenerate", NotificationsCleanup = "NotificationsCleanup", @@ -7161,7 +7286,7 @@ export enum JobName { VersionCheck = "VersionCheck", OcrQueueAll = "OcrQueueAll", Ocr = "Ocr", - WorkflowRun = "WorkflowRun" + WorkflowAssetTrigger = "WorkflowAssetTrigger" } export enum SearchSuggestionType { Country = "country", @@ -7280,7 +7405,6 @@ export enum TranscodeHWAccel { export enum AudioCodec { Mp3 = "mp3", Aac = "aac", - Libopus = "libopus", Opus = "opus", PcmS16Le = "pcm_s16le" } @@ -7330,10 +7454,27 @@ export enum LogLevel { Error = "error", Fatal = "fatal" } +export enum ReleaseChannel { + Stable = "stable", + ReleaseCandidate = "releaseCandidate" +} export enum OAuthTokenEndpointAuthMethod { ClientSecretPost = "client_secret_post", ClientSecretBasic = "client_secret_basic" } +export enum AssetOrderBy { + TakenAt = "takenAt", + CreatedAt = "createdAt" +} +export enum ReleaseType { + Major = "major", + Premajor = "premajor", + Minor = "minor", + Preminor = "preminor", + Patch = "patch", + Prepatch = "prepatch", + Prerelease = "prerelease" +} export enum UserMetadataKey { Preferences = "preferences", License = "license", diff --git a/open-api/typescript-sdk/src/fetch-errors.ts b/packages/sdk/src/fetch-errors.ts similarity index 100% rename from open-api/typescript-sdk/src/fetch-errors.ts rename to packages/sdk/src/fetch-errors.ts diff --git a/open-api/typescript-sdk/src/index.ts b/packages/sdk/src/index.ts similarity index 100% rename from open-api/typescript-sdk/src/index.ts rename to packages/sdk/src/index.ts diff --git a/open-api/typescript-sdk/tsconfig.json b/packages/sdk/tsconfig.json similarity index 100% rename from open-api/typescript-sdk/tsconfig.json rename to packages/sdk/tsconfig.json diff --git a/plugins/.gitignore b/plugins/.gitignore deleted file mode 100644 index 76add878f8..0000000000 --- a/plugins/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -node_modules -dist \ No newline at end of file diff --git a/plugins/LICENSE b/plugins/LICENSE deleted file mode 100644 index 53f0fa6953..0000000000 --- a/plugins/LICENSE +++ /dev/null @@ -1,26 +0,0 @@ -Copyright 2024, The Extism Authors. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -3. Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/plugins/esbuild.js b/plugins/esbuild.js deleted file mode 100644 index 04cb6e85aa..0000000000 --- a/plugins/esbuild.js +++ /dev/null @@ -1,12 +0,0 @@ -const esbuild = require('esbuild'); - -esbuild - .build({ - entryPoints: ['src/index.ts'], - outdir: 'dist', - bundle: true, - sourcemap: true, - minify: false, // might want to use true for production build - format: 'cjs', // needs to be CJS for now - target: ['es2020'] // don't go over es2020 because quickjs doesn't support it - }) \ No newline at end of file diff --git a/plugins/manifest.json b/plugins/manifest.json deleted file mode 100644 index 4d2de275ca..0000000000 --- a/plugins/manifest.json +++ /dev/null @@ -1,159 +0,0 @@ -{ - "name": "immich-core", - "version": "2.0.1", - "title": "Immich Core", - "description": "Core workflow capabilities for Immich", - "author": "Immich Team", - "wasm": { - "path": "dist/plugin.wasm" - }, - "filters": [ - { - "methodName": "filterFileName", - "title": "Filter by filename", - "description": "Filter assets by filename pattern using text matching or regular expressions", - "supportedContexts": [ - "asset" - ], - "schema": { - "type": "object", - "properties": { - "pattern": { - "type": "string", - "title": "Filename pattern", - "description": "Text or regex pattern to match against filename" - }, - "matchType": { - "type": "string", - "title": "Match type", - "enum": [ - "contains", - "regex", - "exact" - ], - "default": "contains", - "description": "Type of pattern matching to perform" - }, - "caseSensitive": { - "type": "boolean", - "default": false, - "description": "Whether matching should be case-sensitive" - } - }, - "required": [ - "pattern" - ] - } - }, - { - "methodName": "filterFileType", - "title": "Filter by file type", - "description": "Filter assets by file type", - "supportedContexts": [ - "asset" - ], - "schema": { - "type": "object", - "properties": { - "fileTypes": { - "type": "array", - "title": "File types", - "items": { - "type": "string", - "enum": [ - "image", - "video" - ] - }, - "description": "Allowed file types" - } - }, - "required": [ - "fileTypes" - ] - } - }, - { - "methodName": "filterPerson", - "title": "Filter by person", - "description": "Filter by detected person", - "supportedContexts": [ - "person" - ], - "schema": { - "type": "object", - "properties": { - "personIds": { - "type": "array", - "title": "Person IDs", - "items": { - "type": "string" - }, - "description": "List of person to match", - "subType": "people-picker" - }, - "matchAny": { - "type": "boolean", - "default": true, - "description": "Match any name (true) or require all names (false)" - } - }, - "required": [ - "personIds" - ] - } - } - ], - "actions": [ - { - "methodName": "actionArchive", - "title": "Archive", - "description": "Move the asset to archive", - "supportedContexts": [ - "asset" - ], - "schema": {} - }, - { - "methodName": "actionFavorite", - "title": "Favorite", - "description": "Mark the asset as favorite or unfavorite", - "supportedContexts": [ - "asset" - ], - "schema": { - "type": "object", - "properties": { - "favorite": { - "type": "boolean", - "default": true, - "description": "Set favorite (true) or unfavorite (false)" - } - } - } - }, - { - "methodName": "actionAddToAlbum", - "title": "Add to Album", - "description": "Add the item to a specified album", - "supportedContexts": [ - "asset", - "person" - ], - "schema": { - "type": "object", - "properties": { - "albumId": { - "type": "string", - "title": "Album ID", - "description": "Target album ID", - "subType": "album-picker" - } - }, - "required": [ - "albumId" - ] - } - } - ] -} diff --git a/plugins/mise.toml b/plugins/mise.toml deleted file mode 100644 index 66a107674d..0000000000 --- a/plugins/mise.toml +++ /dev/null @@ -1,11 +0,0 @@ -[tools] -"github:extism/cli" = "1.6.3" -"github:webassembly/binaryen" = "version_124" -"github:extism/js-pdk" = "1.6.0" - -[tasks.install] -run = "pnpm install --frozen-lockfile" - -[tasks.build] -depends = ["install"] -run = "pnpm run build" diff --git a/plugins/package-lock.json b/plugins/package-lock.json deleted file mode 100644 index d74b830799..0000000000 --- a/plugins/package-lock.json +++ /dev/null @@ -1,533 +0,0 @@ -{ - "name": "plugins", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "plugins", - "version": "1.0.0", - "license": "AGPL-3.0", - "devDependencies": { - "@extism/js-pdk": "^1.0.1", - "esbuild": "^0.28.0", - "typescript": "^6.0.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", - "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", - "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", - "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", - "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", - "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", - "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", - "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", - "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", - "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", - "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", - "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", - "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", - "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", - "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", - "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", - "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", - "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", - "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", - "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", - "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", - "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", - "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", - "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", - "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", - "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", - "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@extism/js-pdk": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@extism/js-pdk/-/js-pdk-1.1.1.tgz", - "integrity": "sha512-VZLn/dX0ttA1uKk2PZeR/FL3N+nA1S5Vc7E5gdjkR60LuUIwCZT9cYON245V4HowHlBA7YOegh0TLjkx+wNbrA==", - "dev": true, - "license": "BSD-Clause-3", - "dependencies": { - "urlpattern-polyfill": "^8.0.2" - } - }, - "node_modules/esbuild": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", - "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.28.0", - "@esbuild/android-arm": "0.28.0", - "@esbuild/android-arm64": "0.28.0", - "@esbuild/android-x64": "0.28.0", - "@esbuild/darwin-arm64": "0.28.0", - "@esbuild/darwin-x64": "0.28.0", - "@esbuild/freebsd-arm64": "0.28.0", - "@esbuild/freebsd-x64": "0.28.0", - "@esbuild/linux-arm": "0.28.0", - "@esbuild/linux-arm64": "0.28.0", - "@esbuild/linux-ia32": "0.28.0", - "@esbuild/linux-loong64": "0.28.0", - "@esbuild/linux-mips64el": "0.28.0", - "@esbuild/linux-ppc64": "0.28.0", - "@esbuild/linux-riscv64": "0.28.0", - "@esbuild/linux-s390x": "0.28.0", - "@esbuild/linux-x64": "0.28.0", - "@esbuild/netbsd-arm64": "0.28.0", - "@esbuild/netbsd-x64": "0.28.0", - "@esbuild/openbsd-arm64": "0.28.0", - "@esbuild/openbsd-x64": "0.28.0", - "@esbuild/openharmony-arm64": "0.28.0", - "@esbuild/sunos-x64": "0.28.0", - "@esbuild/win32-arm64": "0.28.0", - "@esbuild/win32-ia32": "0.28.0", - "@esbuild/win32-x64": "0.28.0" - } - }, - "node_modules/typescript": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", - "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/urlpattern-polyfill": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-8.0.2.tgz", - "integrity": "sha512-Qp95D4TPJl1kC9SKigDcqgyM2VDVO4RiJc2d4qe5GrYm+zbIQCWWKAFaJNQ4BhdFeDGwBmAxqJBwWSJDb9T3BQ==", - "dev": true, - "license": "MIT" - } - } -} diff --git a/plugins/src/index.d.ts b/plugins/src/index.d.ts deleted file mode 100644 index 7f805aafe6..0000000000 --- a/plugins/src/index.d.ts +++ /dev/null @@ -1,12 +0,0 @@ -declare module 'main' { - export function filterFileName(): I32; - export function actionAddToAlbum(): I32; - export function actionArchive(): I32; -} - -declare module 'extism:host' { - interface user { - updateAsset(ptr: PTR): I32; - addAssetToAlbum(ptr: PTR): I32; - } -} diff --git a/plugins/src/index.ts b/plugins/src/index.ts deleted file mode 100644 index 5cf666fc87..0000000000 --- a/plugins/src/index.ts +++ /dev/null @@ -1,71 +0,0 @@ -const { updateAsset, addAssetToAlbum } = Host.getFunctions(); - -function parseInput() { - return JSON.parse(Host.inputString()); -} - -function returnOutput(output: any) { - Host.outputString(JSON.stringify(output)); - return 0; -} - -export function filterFileName() { - const input = parseInput(); - const { data, config } = input; - const { pattern, matchType = 'contains', caseSensitive = false } = config; - - const fileName = data.asset.originalFileName || data.asset.fileName || ''; - const searchName = caseSensitive ? fileName : fileName.toLowerCase(); - const searchPattern = caseSensitive ? pattern : pattern.toLowerCase(); - - let passed = false; - - if (matchType === 'exact') { - passed = searchName === searchPattern; - } else if (matchType === 'regex') { - const flags = caseSensitive ? '' : 'i'; - const regex = new RegExp(searchPattern, flags); - passed = regex.test(fileName); - } else { - // contains - passed = searchName.includes(searchPattern); - } - - return returnOutput({ passed }); -} - -export function actionAddToAlbum() { - const input = parseInput(); - const { authToken, config, data } = input; - const { albumId } = config; - - const ptr = Memory.fromString( - JSON.stringify({ - authToken, - assetId: data.asset.id, - albumId: albumId, - }), - ); - - addAssetToAlbum(ptr.offset); - ptr.free(); - - return returnOutput({ success: true }); -} - -export function actionArchive() { - const input = parseInput(); - const { authToken, data } = input; - const ptr = Memory.fromString( - JSON.stringify({ - authToken, - id: data.asset.id, - visibility: 'archive', - }), - ); - - updateAsset(ptr.offset); - ptr.free(); - - return returnOutput({ success: true }); -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c3f970ca51..6aa01f8ace 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,17 +6,24 @@ settings: injectWorkspacePackages: true overrides: - canvas: 2.11.2 + canvas: 3.2.3 sharp: ^0.34.5 webpackbar: ^7.0.0 -packageExtensionsChecksum: sha256-3l4AQg4iuprBDup+q+2JaPvbPg/7XodWCE0ZteH+s54= +packageExtensionsChecksum: sha256-W6pFzyf+6QXnV91iA6oob0OGVkergPXDN1afLgoF53k= pnpmfileChecksum: sha256-un98do36L0wZyqsjcLozQ3YUadCAn2yz5bXcBbOuyDA= importers: - .: {} + .: + devDependencies: + prettier: + specifier: ^3.8.3 + version: 3.8.3 + prettier-plugin-sort-json: + specifier: ^4.2.0 + version: 4.2.0(prettier@3.8.3) .github: devDependencies: @@ -24,117 +31,20 @@ importers: specifier: ^3.7.4 version: 3.8.3 - cli: - dependencies: - chokidar: - specifier: ^4.0.3 - version: 4.0.3 - fast-glob: - specifier: ^3.3.2 - version: 3.3.3 - fastq: - specifier: ^1.17.1 - version: 1.20.1 - lodash-es: - specifier: ^4.17.21 - version: 4.18.1 - micromatch: - specifier: ^4.0.8 - version: 4.0.8 - devDependencies: - '@eslint/js': - specifier: ^10.0.0 - version: 10.0.1(eslint@10.2.1(jiti@2.6.1)) - '@immich/sdk': - specifier: workspace:* - version: link:../open-api/typescript-sdk - '@types/byte-size': - specifier: ^8.1.0 - version: 8.1.2 - '@types/cli-progress': - specifier: ^3.11.0 - version: 3.11.6 - '@types/lodash-es': - specifier: ^4.17.12 - version: 4.17.12 - '@types/micromatch': - specifier: ^4.0.9 - version: 4.0.10 - '@types/mock-fs': - specifier: ^4.13.1 - version: 4.13.4 - '@types/node': - specifier: ^24.12.2 - version: 24.12.2 - '@vitest/coverage-v8': - specifier: ^4.0.0 - version: 4.1.5(vitest@4.1.5) - byte-size: - specifier: ^9.0.0 - version: 9.0.1 - cli-progress: - specifier: ^3.12.0 - version: 3.12.0 - commander: - specifier: ^12.0.0 - version: 12.1.0 - eslint: - specifier: ^10.0.0 - version: 10.2.1(jiti@2.6.1) - eslint-config-prettier: - specifier: ^10.1.8 - version: 10.1.8(eslint@10.2.1(jiti@2.6.1)) - eslint-plugin-prettier: - specifier: ^5.1.3 - version: 5.5.5(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@10.2.1(jiti@2.6.1)))(eslint@10.2.1(jiti@2.6.1))(prettier@3.8.3) - eslint-plugin-unicorn: - specifier: ^64.0.0 - version: 64.0.0(eslint@10.2.1(jiti@2.6.1)) - globals: - specifier: ^17.0.0 - version: 17.5.0 - mock-fs: - specifier: ^5.2.0 - version: 5.5.0 - prettier: - specifier: ^3.7.4 - version: 3.8.3 - prettier-plugin-organize-imports: - specifier: ^4.0.0 - version: 4.3.0(prettier@3.8.3)(typescript@6.0.3) - typescript: - specifier: ^6.0.0 - version: 6.0.3 - typescript-eslint: - specifier: ^8.58.0 - version: 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) - vite: - specifier: ^8.0.0 - version: 8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) - vitest: - specifier: ^4.0.0 - version: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@24.12.2)(@vitest/coverage-v8@4.1.5)(happy-dom@20.9.0)(jsdom@26.1.0(canvas@2.11.2))(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) - vitest-fetch-mock: - specifier: ^0.4.0 - version: 0.4.5(vitest@4.1.5) - yaml: - specifier: ^2.3.1 - version: 2.8.3 - docs: dependencies: '@docusaurus/core': specifier: ~3.10.0 - version: 3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) + version: 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) '@docusaurus/preset-classic': specifier: ~3.10.0 - version: 3.10.0(@algolia/client-search@5.46.0)(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(search-insights@2.17.3)(typescript@6.0.3) + version: 3.10.1(@algolia/client-search@5.52.1)(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(@types/react@19.2.15)(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(search-insights@2.17.3)(typescript@6.0.3) '@docusaurus/theme-common': specifier: ~3.10.0 - version: 3.10.0(@docusaurus/plugin-content-docs@3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3))(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + version: 3.10.1(@docusaurus/plugin-content-docs@3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@docusaurus/theme-mermaid': specifier: ~3.10.0 - version: 3.10.0(@docusaurus/plugin-content-docs@3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3))(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) + version: 3.10.1(@docusaurus/plugin-content-docs@3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3))(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) '@mdi/js': specifier: ^7.3.67 version: 7.4.47 @@ -143,47 +53,47 @@ importers: version: 1.6.1 '@mdx-js/react': specifier: ^3.0.0 - version: 3.1.1(@types/react@19.2.14)(react@19.2.5) + version: 3.1.1(@types/react@19.2.15)(react@19.2.6) autoprefixer: specifier: ^10.4.17 - version: 10.5.0(postcss@8.5.12) + version: 10.5.0(postcss@8.5.15) docusaurus-lunr-search: specifier: ^3.3.2 - version: 3.6.0(@docusaurus/core@3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3))(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + version: 3.6.0(@docusaurus/core@3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6) lunr: specifier: ^2.3.9 version: 2.3.9 postcss: specifier: ^8.4.25 - version: 8.5.12 + version: 8.5.15 prism-react-renderer: specifier: ^2.3.1 - version: 2.4.1(react@19.2.5) + version: 2.4.1(react@19.2.6) raw-loader: specifier: ^4.0.2 - version: 4.0.2(webpack@5.106.2) + version: 4.0.2(webpack@5.107.0(postcss@8.5.15)) react: specifier: ^19.0.0 - version: 19.2.5 + version: 19.2.6 react-dom: specifier: ^19.0.0 - version: 19.2.5(react@19.2.5) + version: 19.2.6(react@19.2.6) tailwindcss: specifier: ^3.2.4 - version: 3.4.19(tsx@4.21.0)(yaml@2.8.3) + version: 3.4.19(tsx@4.22.3)(yaml@2.9.0) url: specifier: ^0.11.0 version: 0.11.4 devDependencies: '@docusaurus/module-type-aliases': specifier: ~3.10.0 - version: 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + version: 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@docusaurus/tsconfig': specifier: ^3.10.0 - version: 3.10.0 + version: 3.10.1 '@docusaurus/types': specifier: ^3.10.0 - version: 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + version: 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) prettier: specifier: ^3.7.4 version: 3.8.3 @@ -195,22 +105,22 @@ importers: devDependencies: '@eslint/js': specifier: ^10.0.0 - version: 10.0.1(eslint@10.2.1(jiti@2.6.1)) + version: 10.0.1(eslint@10.4.0(jiti@2.7.0)) '@faker-js/faker': specifier: ^10.1.0 version: 10.4.0 '@immich/cli': specifier: workspace:* - version: link:../cli + version: link:../packages/cli '@immich/e2e-auth-server': specifier: workspace:* - version: link:../e2e-auth-server + version: link:../packages/e2e-auth-server '@immich/sdk': specifier: workspace:* - version: link:../open-api/typescript-sdk + version: link:../packages/sdk '@playwright/test': specifier: ^1.44.1 - version: 1.59.1 + version: 1.60.0 '@socket.io/component-emitter': specifier: ^3.1.2 version: 3.1.2 @@ -218,8 +128,8 @@ importers: specifier: ^3.4.2 version: 3.7.1 '@types/node': - specifier: ^24.12.2 - version: 24.12.2 + specifier: ^24.12.4 + version: 24.12.4 '@types/pg': specifier: ^8.15.1 version: 8.20.0 @@ -234,28 +144,28 @@ importers: version: 17.4.2 eslint: specifier: ^10.0.0 - version: 10.2.1(jiti@2.6.1) + version: 10.4.0(jiti@2.7.0) eslint-config-prettier: specifier: ^10.1.8 - version: 10.1.8(eslint@10.2.1(jiti@2.6.1)) + version: 10.1.8(eslint@10.4.0(jiti@2.7.0)) eslint-plugin-prettier: specifier: ^5.1.3 - version: 5.5.5(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@10.2.1(jiti@2.6.1)))(eslint@10.2.1(jiti@2.6.1))(prettier@3.8.3) + version: 5.5.5(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@10.4.0(jiti@2.7.0)))(eslint@10.4.0(jiti@2.7.0))(prettier@3.8.3) eslint-plugin-unicorn: specifier: ^64.0.0 - version: 64.0.0(eslint@10.2.1(jiti@2.6.1)) + version: 64.0.0(eslint@10.4.0(jiti@2.7.0)) exiftool-vendored: specifier: ^35.0.0 - version: 35.18.0 + version: 35.20.0 globals: specifier: ^17.0.0 - version: 17.5.0 + version: 17.6.0 luxon: specifier: ^3.4.4 version: 3.7.2 pg: specifier: ^8.11.3 - version: 8.20.0 + version: 8.21.0 pngjs: specifier: ^7.0.0 version: 7.0.0 @@ -279,62 +189,177 @@ importers: version: 6.0.3 typescript-eslint: specifier: ^8.28.0 - version: 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + version: 8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) utimes: specifier: ^5.2.1 - version: 5.2.1(encoding@0.1.13) + version: 5.2.1 vite-tsconfig-paths: specifier: ^6.1.1 - version: 6.1.1(typescript@6.0.3)(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 6.1.1(typescript@6.0.3)(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)) vitest: specifier: ^4.0.0 - version: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@24.12.2)(@vitest/coverage-v8@4.1.5)(happy-dom@20.9.0)(jsdom@26.1.0(canvas@2.11.2(encoding@0.1.13)))(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.1.7(@opentelemetry/api@1.9.1)(@types/node@24.12.4)(@vitest/coverage-v8@4.1.7)(happy-dom@20.9.0)(jsdom@26.1.0(canvas@3.2.3))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)) - e2e-auth-server: + packages/cli: + dependencies: + chokidar: + specifier: ^4.0.3 + version: 4.0.3 + fast-glob: + specifier: ^3.3.2 + version: 3.3.3 + fastq: + specifier: ^1.17.1 + version: 1.20.1 + lodash-es: + specifier: ^4.17.21 + version: 4.18.1 + micromatch: + specifier: ^4.0.8 + version: 4.0.8 + devDependencies: + '@eslint/js': + specifier: ^10.0.0 + version: 10.0.1(eslint@10.4.0(jiti@2.7.0)) + '@immich/sdk': + specifier: workspace:* + version: link:../sdk + '@types/byte-size': + specifier: ^8.1.0 + version: 8.1.2 + '@types/cli-progress': + specifier: ^3.11.0 + version: 3.11.6 + '@types/lodash-es': + specifier: ^4.17.12 + version: 4.17.12 + '@types/micromatch': + specifier: ^4.0.9 + version: 4.0.10 + '@types/mock-fs': + specifier: ^4.13.1 + version: 4.13.4 + '@types/node': + specifier: ^24.12.4 + version: 24.12.4 + '@vitest/coverage-v8': + specifier: ^4.0.0 + version: 4.1.7(vitest@4.1.7) + byte-size: + specifier: ^9.0.0 + version: 9.0.1 + cli-progress: + specifier: ^3.12.0 + version: 3.12.0 + commander: + specifier: ^12.0.0 + version: 12.1.0 + eslint: + specifier: ^10.0.0 + version: 10.4.0(jiti@2.7.0) + eslint-config-prettier: + specifier: ^10.1.8 + version: 10.1.8(eslint@10.4.0(jiti@2.7.0)) + eslint-plugin-prettier: + specifier: ^5.1.3 + version: 5.5.5(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@10.4.0(jiti@2.7.0)))(eslint@10.4.0(jiti@2.7.0))(prettier@3.8.3) + eslint-plugin-unicorn: + specifier: ^64.0.0 + version: 64.0.0(eslint@10.4.0(jiti@2.7.0)) + globals: + specifier: ^17.0.0 + version: 17.6.0 + mock-fs: + specifier: ^5.2.0 + version: 5.5.0 + prettier: + specifier: ^3.7.4 + version: 3.8.3 + prettier-plugin-organize-imports: + specifier: ^4.0.0 + version: 4.3.0(prettier@3.8.3)(typescript@6.0.3) + typescript: + specifier: ^6.0.0 + version: 6.0.3 + typescript-eslint: + specifier: ^8.58.0 + version: 8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) + vite: + specifier: ^8.0.0 + version: 8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0) + vitest: + specifier: ^4.0.0 + version: 4.1.7(@opentelemetry/api@1.9.1)(@types/node@24.12.4)(@vitest/coverage-v8@4.1.7)(happy-dom@20.9.0)(jsdom@26.1.0(canvas@3.2.3))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)) + vitest-fetch-mock: + specifier: ^0.4.0 + version: 0.4.5(vitest@4.1.7) + yaml: + specifier: ^2.3.1 + version: 2.9.0 + + packages/e2e-auth-server: devDependencies: '@types/oidc-provider': specifier: ^9.0.0 version: 9.5.0 jose: specifier: ^6.0.0 - version: 6.2.2 + version: 6.2.3 oidc-provider: specifier: ^9.0.0 - version: 9.8.2 + version: 9.8.3 tsx: specifier: ^4.20.6 - version: 4.21.0 + version: 4.22.3 - i18n: + packages/plugin-core: devDependencies: - prettier: - specifier: ^3.7.4 - version: 3.8.3 - prettier-plugin-sort-json: - specifier: ^4.1.1 - version: 4.2.0(prettier@3.8.3) + '@extism/js-pdk': + specifier: ^1.0.1 + version: 1.1.1 + '@immich/plugin-sdk': + specifier: workspace:* + version: link:../plugin-sdk + '@immich/sdk': + specifier: workspace:* + version: link:../sdk + esbuild: + specifier: ^0.28.0 + version: 0.28.0 + typescript: + specifier: ^6.0.0 + version: 6.0.3 - open-api/typescript-sdk: + packages/plugin-sdk: + devDependencies: + '@extism/js-pdk': + specifier: ^1.1.1 + version: 1.1.1 + '@immich/sdk': + specifier: workspace:* + version: link:../sdk + '@types/node': + specifier: ^24.12.4 + version: 24.12.4 + esbuild: + specifier: ^0.28.0 + version: 0.28.0 + tsc-alias: + specifier: ^1.8.16 + version: 1.8.17 + typescript: + specifier: ^5.9.3 + version: 5.9.3 + + packages/sdk: dependencies: '@oazapfts/runtime': specifier: ^1.0.2 version: 1.2.0 devDependencies: '@types/node': - specifier: ^24.12.2 - version: 24.12.2 - typescript: - specifier: ^6.0.0 - version: 6.0.3 - - plugins: - devDependencies: - '@extism/js-pdk': - specifier: ^1.0.1 - version: 1.1.1 - esbuild: - specifier: ^0.28.0 - version: 0.28.0 + specifier: ^24.12.4 + version: 24.12.4 typescript: specifier: ^6.0.0 version: 6.0.3 @@ -344,75 +369,78 @@ importers: '@extism/extism': specifier: 2.0.0-rc13 version: 2.0.0-rc13 + '@immich/plugin-sdk': + specifier: workspace:* + version: link:../packages/plugin-sdk '@immich/sql-tools': specifier: ^0.5.1 version: 0.5.2 '@nestjs/bullmq': specifier: ^11.0.1 - version: 11.0.4(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(bullmq@5.76.1) + version: 11.0.4(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(bullmq@5.76.10) '@nestjs/common': specifier: ^11.0.4 - version: 11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + version: 11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nestjs/core': specifier: ^11.0.4 - version: 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) + version: 11.1.21(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.21)(@nestjs/websockets@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nestjs/platform-express': specifier: ^11.0.4 - version: 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19) + version: 11.1.21(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21) '@nestjs/platform-socket.io': specifier: ^11.0.4 - version: 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.19)(rxjs@7.8.2) + version: 11.1.21(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.21)(rxjs@7.8.2) '@nestjs/schedule': specifier: ^6.0.0 - version: 6.1.3(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19) + version: 6.1.3(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21) '@nestjs/swagger': specifier: ^11.4.2 - version: 11.4.2(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(class-transformer@0.5.1)(reflect-metadata@0.2.2) + version: 11.4.3(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(reflect-metadata@0.2.2)(typescript@6.0.3) '@nestjs/websockets': specifier: ^11.0.4 - version: 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/platform-socket.io@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) + version: 11.1.21(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(@nestjs/platform-socket.io@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@opentelemetry/api': specifier: ^1.9.0 version: 1.9.1 '@opentelemetry/context-async-hooks': specifier: ^2.0.0 - version: 2.7.0(@opentelemetry/api@1.9.1) + version: 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/exporter-prometheus': - specifier: ^0.215.0 - version: 0.215.0(@opentelemetry/api@1.9.1) + specifier: ^0.218.0 + version: 0.218.0(@opentelemetry/api@1.9.1) '@opentelemetry/instrumentation-http': - specifier: ^0.215.0 - version: 0.215.0(@opentelemetry/api@1.9.1) + specifier: ^0.218.0 + version: 0.218.0(@opentelemetry/api@1.9.1) '@opentelemetry/instrumentation-ioredis': - specifier: ^0.63.0 - version: 0.63.0(@opentelemetry/api@1.9.1) + specifier: ^0.66.0 + version: 0.66.0(@opentelemetry/api@1.9.1) '@opentelemetry/instrumentation-nestjs-core': - specifier: ^0.61.0 - version: 0.61.0(@opentelemetry/api@1.9.1) + specifier: ^0.64.0 + version: 0.64.0(@opentelemetry/api@1.9.1) '@opentelemetry/instrumentation-pg': - specifier: ^0.67.0 - version: 0.67.0(@opentelemetry/api@1.9.1) + specifier: ^0.70.0 + version: 0.70.0(@opentelemetry/api@1.9.1) '@opentelemetry/resources': specifier: ^2.0.1 - version: 2.7.0(@opentelemetry/api@1.9.1) + version: 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/sdk-metrics': specifier: ^2.0.1 - version: 2.7.0(@opentelemetry/api@1.9.1) + version: 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/sdk-node': - specifier: ^0.215.0 - version: 0.215.0(@opentelemetry/api@1.9.1) + specifier: ^0.218.0 + version: 0.218.0(@opentelemetry/api@1.9.1) '@opentelemetry/semantic-conventions': specifier: ^1.34.0 - version: 1.40.0 + version: 1.41.1 '@react-email/components': specifier: ^1.0.0 - version: 1.0.12(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + version: 1.0.12(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@react-email/render': specifier: ^2.0.0 - version: 2.0.7(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + version: 2.0.8(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@socket.io/redis-adapter': specifier: ^8.3.0 - version: 8.3.0(socket.io-adapter@2.5.6) + version: 8.3.0(socket.io-adapter@2.5.7) archiver: specifier: ^7.0.0 version: 7.0.1 @@ -427,7 +455,7 @@ importers: version: 2.2.2 bullmq: specifier: ^5.51.0 - version: 5.76.1 + version: 5.76.10 chokidar: specifier: ^4.0.3 version: 4.0.3 @@ -444,8 +472,8 @@ importers: specifier: 4.4.0 version: 4.4.0 exiftool-vendored: - specifier: ^35.0.0 - version: 35.18.0 + specifier: ^35.20.0 + version: 35.20.0 express: specifier: ^5.1.0 version: 5.2.1 @@ -455,9 +483,12 @@ importers: fluent-ffmpeg: specifier: ^2.1.2 version: 2.1.3 + generic-pool: + specifier: ^3.9.0 + version: 3.9.0 geo-tz: specifier: ^8.0.0 - version: 8.1.6 + version: 8.1.7 handlebars: specifier: ^4.7.8 version: 4.7.9 @@ -472,7 +503,7 @@ importers: version: 5.10.1 jose: specifier: ^6.0.0 - version: 6.2.2 + version: 6.2.3 js-yaml: specifier: ^4.1.0 version: 4.1.1 @@ -480,11 +511,11 @@ importers: specifier: ^9.0.2 version: 9.0.3 kysely: - specifier: 0.28.16 - version: 0.28.16 + specifier: 0.28.17 + version: 0.28.17 kysely-postgres-js: specifier: ^3.0.0 - version: 3.0.0(kysely@0.28.16)(postgres@3.4.9) + version: 3.0.0(kysely@0.28.17)(postgres@3.4.9) lodash: specifier: ^4.17.21 version: 4.18.1 @@ -493,34 +524,34 @@ importers: version: 3.7.2 mnemonist: specifier: ^0.40.3 - version: 0.40.3 + version: 0.40.4 multer: specifier: ^2.0.2 version: 2.1.1 nest-commander: specifier: ^3.16.0 - version: 3.20.1(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@types/inquirer@8.2.12)(@types/node@24.12.2)(typescript@6.0.3) + version: 3.20.1(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(@types/inquirer@8.2.12)(@types/node@24.12.4)(typescript@6.0.3) nestjs-cls: specifier: ^6.0.0 - version: 6.2.0(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) + version: 6.2.0(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2) nestjs-kysely: specifier: 3.1.2 - version: 3.1.2(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(kysely@0.28.16)(reflect-metadata@0.2.2) + version: 3.1.2(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(kysely@0.28.17)(reflect-metadata@0.2.2) nestjs-otel: - specifier: ^7.0.0 - version: 7.0.1(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19) + specifier: ^8.0.0 + version: 8.0.3(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21) nestjs-zod: specifier: ^5.3.0 - version: 5.3.0(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/swagger@11.4.2(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(class-transformer@0.5.1)(reflect-metadata@0.2.2))(rxjs@7.8.2)(zod@4.3.6) + version: 5.4.0(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/swagger@11.4.3(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(reflect-metadata@0.2.2)(typescript@6.0.3))(rxjs@7.8.2)(zod@4.3.6) nodemailer: specifier: ^8.0.0 - version: 8.0.5 + version: 8.0.7 openid-client: specifier: ^6.3.3 - version: 6.8.3 + version: 6.8.4 pg: specifier: ^8.11.3 - version: 8.20.0 + version: 8.21.0 picomatch: specifier: ^4.0.2 version: 4.0.4 @@ -529,10 +560,10 @@ importers: version: 3.4.9 react: specifier: ^19.0.0 - version: 19.2.5 + version: 19.2.6 react-dom: specifier: ^19.0.0 - version: 19.2.5(react@19.2.5) + version: 19.2.6(react@19.2.6) react-email: specifier: ^5.0.0 version: 5.2.11 @@ -546,8 +577,8 @@ importers: specifier: ^1.6.3 version: 1.6.4 semver: - specifier: ^7.6.2 - version: 7.7.4 + specifier: ^7.8.1 + version: 7.8.1 sharp: specifier: ^0.34.5 version: 0.34.5 @@ -559,7 +590,7 @@ importers: version: 4.8.3 tailwindcss-preset-email: specifier: ^1.4.0 - version: 1.4.1(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.3)) + version: 1.4.1(tailwindcss@3.4.19(tsx@4.22.3)(yaml@2.9.0)) thumbhash: specifier: ^0.1.1 version: 0.1.1 @@ -576,24 +607,24 @@ importers: specifier: ^13.12.0 version: 13.15.35 zod: - specifier: ^4.3.6 + specifier: 4.3.6 version: 4.3.6 devDependencies: '@eslint/js': specifier: ^10.0.0 - version: 10.0.1(eslint@10.2.1(jiti@2.6.1)) + version: 10.0.1(eslint@10.4.0(jiti@2.7.0)) '@nestjs/cli': specifier: ^11.0.2 - version: 11.0.21(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@24.12.2)(esbuild@0.28.0)(prettier@3.8.3) + version: 11.0.21(@swc/core@1.15.33(@swc/helpers@0.5.23))(@types/node@24.12.4)(esbuild@0.28.0)(lightningcss@1.32.0)(prettier@3.8.3) '@nestjs/schematics': specifier: ^11.0.0 version: 11.1.0(chokidar@4.0.3)(prettier@3.8.3)(typescript@6.0.3) '@nestjs/testing': specifier: ^11.0.4 - version: 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/platform-express@11.1.19) + version: 11.1.21(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(@nestjs/platform-express@11.1.21) '@swc/core': specifier: ^1.4.14 - version: 1.15.30(@swc/helpers@0.5.21) + version: 1.15.33(@swc/helpers@0.5.23) '@types/archiver': specifier: ^7.0.0 version: 7.0.0 @@ -637,8 +668,8 @@ importers: specifier: ^2.0.0 version: 2.1.0 '@types/node': - specifier: ^24.12.2 - version: 24.12.2 + specifier: ^24.12.4 + version: 24.12.4 '@types/nodemailer': specifier: ^8.0.0 version: 8.0.0 @@ -650,7 +681,7 @@ importers: version: 6.0.5 '@types/react': specifier: ^19.0.0 - version: 19.2.14 + version: 19.2.15 '@types/semver': specifier: ^7.5.8 version: 7.7.1 @@ -664,23 +695,23 @@ importers: specifier: ^13.15.2 version: 13.15.10 '@vitest/coverage-v8': - specifier: ^3.0.0 - version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.12.2)(happy-dom@20.9.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.32.0)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + specifier: ^4.0.0 + version: 4.1.7(vitest@3.2.4(@types/debug@4.1.13)(@types/node@24.12.4)(happy-dom@20.9.0)(jiti@2.7.0)(jsdom@26.1.0(canvas@3.2.3))(lightningcss@1.32.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)) eslint: specifier: ^10.0.0 - version: 10.2.1(jiti@2.6.1) + version: 10.4.0(jiti@2.7.0) eslint-config-prettier: specifier: ^10.1.8 - version: 10.1.8(eslint@10.2.1(jiti@2.6.1)) + version: 10.1.8(eslint@10.4.0(jiti@2.7.0)) eslint-plugin-prettier: specifier: ^5.1.3 - version: 5.5.5(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@10.2.1(jiti@2.6.1)))(eslint@10.2.1(jiti@2.6.1))(prettier@3.8.3) + version: 5.5.5(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@10.4.0(jiti@2.7.0)))(eslint@10.4.0(jiti@2.7.0))(prettier@3.8.3) eslint-plugin-unicorn: specifier: ^64.0.0 - version: 64.0.0(eslint@10.2.1(jiti@2.6.1)) + version: 64.0.0(eslint@10.4.0(jiti@2.7.0)) globals: specifier: ^17.0.0 - version: 17.5.0 + version: 17.6.0 mock-fs: specifier: ^5.2.0 version: 5.5.0 @@ -695,46 +726,46 @@ importers: version: 4.3.0(prettier@3.8.3)(typescript@6.0.3) sql-formatter: specifier: ^15.0.0 - version: 15.7.3 + version: 15.8.0 supertest: specifier: ^7.1.0 version: 7.2.2 tailwindcss: specifier: ^3.4.0 - version: 3.4.19(tsx@4.21.0)(yaml@2.8.3) + version: 3.4.19(tsx@4.22.3)(yaml@2.9.0) testcontainers: - specifier: ^11.0.0 - version: 11.14.0 + specifier: ^12.0.0 + version: 12.0.1 typescript: specifier: ^6.0.0 version: 6.0.3 typescript-eslint: specifier: ^8.28.0 - version: 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + version: 8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) unplugin-swc: specifier: ^1.4.5 - version: 1.5.9(@swc/core@1.15.30(@swc/helpers@0.5.21))(rollup@4.55.1) + version: 1.5.9(@swc/core@1.15.33(@swc/helpers@0.5.23))(rollup@4.60.4) vite-tsconfig-paths: specifier: ^6.0.0 - version: 6.1.1(typescript@6.0.3)(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 6.1.1(typescript@6.0.3)(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)) vitest: specifier: ^3.0.0 - version: 3.2.4(@types/debug@4.1.12)(@types/node@24.12.2)(happy-dom@20.9.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.32.0)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + version: 3.2.4(@types/debug@4.1.13)(@types/node@24.12.4)(happy-dom@20.9.0)(jiti@2.7.0)(jsdom@26.1.0(canvas@3.2.3))(lightningcss@1.32.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0) web: dependencies: '@formatjs/icu-messageformat-parser': specifier: ^3.0.0 - version: 3.5.4 + version: 3.5.9 '@immich/justified-layout-wasm': specifier: ^0.4.3 version: 0.4.3 '@immich/sdk': specifier: workspace:* - version: link:../open-api/typescript-sdk + version: link:../packages/sdk '@immich/ui': - specifier: ^0.76.0 - version: 0.76.2(@sveltejs/kit@2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2) + specifier: ^0.79.2 + version: 0.79.3(@sveltejs/kit@2.60.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.55.8(@typescript-eslint/types@8.59.4))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4))(typescript@6.0.3)(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4)) '@mapbox/mapbox-gl-rtl-text': specifier: 0.4.0 version: 0.4.0 @@ -770,13 +801,13 @@ importers: version: 0.42.0 '@zoom-image/svelte': specifier: ^0.3.0 - version: 0.3.9(svelte@5.55.2) + version: 0.3.9(svelte@5.55.8(@typescript-eslint/types@8.59.4)) dom-to-image: specifier: ^2.6.0 version: 2.6.0 fabric: specifier: ^7.0.0 - version: 7.3.1 + version: 7.4.0 geo-coordinates-parser: specifier: ^1.7.4 version: 1.7.4 @@ -789,9 +820,15 @@ importers: happy-dom: specifier: ^20.0.0 version: 20.9.0 + hls-video-element: + specifier: ^1.5.11 + version: 1.5.11 + hls.js: + specifier: ^1.6.16 + version: 1.6.16 intl-messageformat: specifier: ^11.0.0 - version: 11.2.1 + version: 11.2.6 justified-layout: specifier: ^4.1.0 version: 4.1.0 @@ -806,7 +843,7 @@ importers: version: 5.24.0 media-chrome: specifier: ^4.19.0 - version: 4.19.0(react@19.2.5) + version: 4.19.0(react@19.2.6) pmtiles: specifier: ^4.3.0 version: 4.4.1 @@ -815,7 +852,7 @@ importers: version: 1.5.4 simple-icons: specifier: ^16.0.0 - version: 16.17.0 + version: 16.20.0 socket.io-client: specifier: ~4.8.0 version: 4.8.3 @@ -824,25 +861,25 @@ importers: version: 5.2.2 svelte-i18n: specifier: ^4.0.1 - version: 4.0.1(svelte@5.55.2) + version: 4.0.1(svelte@5.55.8(@typescript-eslint/types@8.59.4)) svelte-jsoneditor: specifier: ^3.10.0 - version: 3.12.0(svelte@5.55.2) + version: 3.12.0(svelte@5.55.8(@typescript-eslint/types@8.59.4)) svelte-maplibre: specifier: ^1.2.5 - version: 1.3.0(svelte@5.55.2) + version: 1.3.0(svelte@5.55.8(@typescript-eslint/types@8.59.4)) svelte-persisted-store: specifier: ^0.12.0 - version: 0.12.0(svelte@5.55.2) + version: 0.12.0(svelte@5.55.8(@typescript-eslint/types@8.59.4)) tabbable: specifier: ^6.2.0 version: 6.4.0 tailwind-merge: specifier: ^3.5.0 - version: 3.5.0 + version: 3.6.0 tailwind-variants: specifier: ^3.2.2 - version: 3.2.2(tailwind-merge@3.5.0)(tailwindcss@4.2.4) + version: 3.2.2(tailwind-merge@3.6.0)(tailwindcss@4.3.0) thumbhash: specifier: ^0.1.1 version: 0.1.1 @@ -855,43 +892,43 @@ importers: devDependencies: '@eslint/js': specifier: ^10.0.0 - version: 10.0.1(eslint@10.2.1(jiti@2.6.1)) + version: 10.0.1(eslint@10.4.0(jiti@2.7.0)) '@faker-js/faker': specifier: ^10.0.0 version: 10.4.0 '@koddsson/eslint-plugin-tscompat': specifier: ^0.2.0 - version: 0.2.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + version: 0.2.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) '@socket.io/component-emitter': specifier: ^3.1.0 version: 3.1.2 '@sveltejs/adapter-static': specifier: ^3.0.8 - version: 3.0.10(@sveltejs/kit@2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))) + version: 3.0.10(@sveltejs/kit@2.60.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.55.8(@typescript-eslint/types@8.59.4))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4))(typescript@6.0.3)(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0))) '@sveltejs/enhanced-img': specifier: ^0.10.4 - version: 0.10.4(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(rollup@4.55.1)(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 0.10.4(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.55.8(@typescript-eslint/types@8.59.4))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(rollup@4.60.4)(svelte@5.55.8(@typescript-eslint/types@8.59.4))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)) '@sveltejs/kit': specifier: ^2.56.1 - version: 2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 2.60.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.55.8(@typescript-eslint/types@8.59.4))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4))(typescript@6.0.3)(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)) '@sveltejs/vite-plugin-svelte': - specifier: 7.0.0 - version: 7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + specifier: 7.1.2 + version: 7.1.2(svelte@5.55.8(@typescript-eslint/types@8.59.4))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)) '@tailwindcss/vite': specifier: ^4.2.4 - version: 4.2.4(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.3.0(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)) '@testing-library/jest-dom': specifier: ^6.4.2 version: 6.9.1 '@testing-library/svelte': specifier: ^5.2.8 - version: 5.3.1(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.5) + version: 5.3.1(svelte@5.55.8(@typescript-eslint/types@8.59.4))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0))(vitest@4.1.7) '@testing-library/user-event': specifier: ^14.5.2 version: 14.6.1(@testing-library/dom@10.4.1) '@trivago/prettier-plugin-sort-imports': specifier: ^6.0.2 - version: 6.0.2(prettier-plugin-svelte@3.5.1(prettier@3.8.3)(svelte@5.55.2))(prettier@3.8.3)(svelte@5.55.2) + version: 6.0.2(prettier-plugin-svelte@4.1.0(prettier@3.8.3)(svelte@5.55.8(@typescript-eslint/types@8.59.4)))(prettier@3.8.3)(svelte@5.55.8(@typescript-eslint/types@8.59.4)) '@types/chromecast-caf-sender': specifier: ^1.0.11 version: 1.0.11 @@ -912,34 +949,34 @@ importers: version: 1.5.6 '@vitest/coverage-v8': specifier: ^4.0.0 - version: 4.1.5(vitest@4.1.5) + version: 4.1.7(vitest@4.1.7) dotenv: specifier: ^17.0.0 version: 17.4.2 eslint: specifier: ^10.2.1 - version: 10.2.1(jiti@2.6.1) + version: 10.4.0(jiti@2.7.0) eslint-config-prettier: specifier: ^10.1.8 - version: 10.1.8(eslint@10.2.1(jiti@2.6.1)) + version: 10.1.8(eslint@10.4.0(jiti@2.7.0)) eslint-plugin-better-tailwindcss: specifier: ^4.5.0 - version: 4.5.0(eslint@10.2.1(jiti@2.6.1))(tailwindcss@4.2.4)(typescript@6.0.3) + version: 4.5.0(eslint@10.4.0(jiti@2.7.0))(tailwindcss@4.3.0)(typescript@6.0.3) eslint-plugin-compat: specifier: ^7.0.0 - version: 7.0.1(eslint@10.2.1(jiti@2.6.1)) + version: 7.0.2(eslint@10.4.0(jiti@2.7.0)) eslint-plugin-svelte: specifier: ^3.12.4 - version: 3.17.1(eslint@10.2.1(jiti@2.6.1))(svelte@5.55.2) + version: 3.17.1(eslint@10.4.0(jiti@2.7.0))(svelte@5.55.8(@typescript-eslint/types@8.59.4)) eslint-plugin-unicorn: specifier: ^64.0.0 - version: 64.0.0(eslint@10.2.1(jiti@2.6.1)) + version: 64.0.0(eslint@10.4.0(jiti@2.7.0)) factory.ts: specifier: ^1.4.1 version: 1.4.2 globals: specifier: ^17.0.0 - version: 17.5.0 + version: 17.6.0 prettier: specifier: ^3.7.4 version: 3.8.3 @@ -947,148 +984,132 @@ importers: specifier: ^4.1.1 version: 4.2.0(prettier@3.8.3) prettier-plugin-svelte: - specifier: ^3.3.3 - version: 3.5.1(prettier@3.8.3)(svelte@5.55.2) + specifier: ^4.0.0 + version: 4.1.0(prettier@3.8.3)(svelte@5.55.8(@typescript-eslint/types@8.59.4)) rollup-plugin-visualizer: specifier: ^7.0.0 - version: 7.0.1(rolldown@1.0.0-rc.17)(rollup@4.55.1) + version: 7.0.1(rolldown@1.0.1)(rollup@4.60.4) svelte: - specifier: 5.55.2 - version: 5.55.2 + specifier: 5.55.8 + version: 5.55.8(@typescript-eslint/types@8.59.4) svelte-check: specifier: ^4.4.6 - version: 4.4.6(picomatch@4.0.4)(svelte@5.55.2)(typescript@6.0.3) + version: 4.4.8(picomatch@4.0.4)(svelte@5.55.8(@typescript-eslint/types@8.59.4))(typescript@6.0.3) svelte-eslint-parser: specifier: ^1.3.3 - version: 1.6.0(svelte@5.55.2) + version: 1.6.1(svelte@5.55.8(@typescript-eslint/types@8.59.4)) tailwindcss: specifier: ^4.2.4 - version: 4.2.4 + version: 4.3.0 typescript: specifier: ^6.0.0 version: 6.0.3 typescript-eslint: specifier: ^8.45.0 - version: 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + version: 8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) vite: specifier: ^8.0.0 - version: 8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + version: 8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0) vitest: specifier: ^4.0.0 - version: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(@vitest/coverage-v8@4.1.5)(happy-dom@20.9.0)(jsdom@26.1.0(canvas@2.11.2))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.1.7(@opentelemetry/api@1.9.1)(@types/node@24.12.4)(@vitest/coverage-v8@4.1.7)(happy-dom@20.9.0)(jsdom@26.1.0(canvas@3.2.3))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)) packages: '@adobe/css-tools@4.4.4': resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==} - '@ai-sdk/gateway@2.0.21': - resolution: {integrity: sha512-BwV7DU/lAm3Xn6iyyvZdWgVxgLu3SNXzl5y57gMvkW4nGhAOV5269IrJzQwGt03bb107sa6H6uJwWxc77zXoGA==} - engines: {node: '>=18'} - peerDependencies: - zod: ^3.25.76 || ^4.1.8 - - '@ai-sdk/provider-utils@3.0.19': - resolution: {integrity: sha512-W41Wc9/jbUVXVwCN/7bWa4IKe8MtxO3EyA0Hfhx6grnmiYlCvpI8neSYWFE0zScXJkgA/YK3BRybzgyiXuu6JA==} - engines: {node: '>=18'} - peerDependencies: - zod: ^3.25.76 || ^4.1.8 - - '@ai-sdk/provider@2.0.0': - resolution: {integrity: sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA==} - engines: {node: '>=18'} - - '@ai-sdk/react@2.0.115': - resolution: {integrity: sha512-Etu7gWSEi2dmXss1PoR5CAZGwGShXsF9+Pon1eRO6EmatjYaBMhq1CfHPyYhGzWrint8jJIK2VaAhiMef29qZw==} - engines: {node: '>=18'} - peerDependencies: - react: ^18 || ~19.0.1 || ~19.1.2 || ^19.2.1 - zod: ^3.25.76 || ^4.1.8 - peerDependenciesMeta: - zod: - optional: true - - '@algolia/abtesting@1.12.0': - resolution: {integrity: sha512-EfW0bfxjPs+C7ANkJDw2TATntfBKsFiy7APh+KO0pQ8A6HYa5I0NjFuCGCXWfzzzLXNZta3QUl3n5Kmm6aJo9Q==} + '@algolia/abtesting@1.18.1': + resolution: {integrity: sha512-aehCadlWOGvrT91KUIZpC0MbB8KBW9yUuvTJFd2xesR7le/IsT4nJUnjCCZ4ZqZCeTcPHPV5mo//fZ5oxcSVYw==} engines: {node: '>= 14.0.0'} '@algolia/autocomplete-core@1.19.2': resolution: {integrity: sha512-mKv7RyuAzXvwmq+0XRK8HqZXt9iZ5Kkm2huLjgn5JoCPtDy+oh9yxUMfDDaVCw0oyzZ1isdJBc7l9nuCyyR7Nw==} + '@algolia/autocomplete-core@1.19.8': + resolution: {integrity: sha512-3YEorYg44niXcm7gkft3nXYItHd44e8tmh4D33CTszPgP0QWkaLEaFywiNyJBo7UL/mqObA/G9RYuU7R8tN1IA==} + '@algolia/autocomplete-plugin-algolia-insights@1.19.2': resolution: {integrity: sha512-TjxbcC/r4vwmnZaPwrHtkXNeqvlpdyR+oR9Wi2XyfORkiGkLTVhX2j+O9SaCCINbKoDfc+c2PB8NjfOnz7+oKg==} peerDependencies: search-insights: '>= 1 < 3' + '@algolia/autocomplete-plugin-algolia-insights@1.19.8': + resolution: {integrity: sha512-ZvJWO8ZZJDpc1LNM2TTBdmQsZBLMR4rU5iNR2OYvEeFBiaf/0ESnRSSLQbryarJY4SVxtoz6A2ZtDMNM+iQEAA==} + peerDependencies: + search-insights: '>= 1 < 3' + '@algolia/autocomplete-shared@1.19.2': resolution: {integrity: sha512-jEazxZTVD2nLrC+wYlVHQgpBoBB5KPStrJxLzsIFl6Kqd1AlG9sIAGl39V5tECLpIQzB3Qa2T6ZPJ1ChkwMK/w==} peerDependencies: '@algolia/client-search': '>= 4.9.1 < 6' algoliasearch: '>= 4.9.1 < 6' - '@algolia/client-abtesting@5.46.0': - resolution: {integrity: sha512-eG5xV8rujK4ZIHXrRshvv9O13NmU/k42Rnd3w43iKH5RaQ2zWuZO6Q7XjaoJjAFVCsJWqRbXzbYyPGrbF3wGNg==} + '@algolia/autocomplete-shared@1.19.8': + resolution: {integrity: sha512-h5hf2t8ejF6vlOgvLaZzQbWs5SyH2z4PAWygNAvvD/2RI29hdQ54ldUGwqVuj9Srs+n8XUKTPUqb7fvhBhQrnQ==} + peerDependencies: + '@algolia/client-search': '>= 4.9.1 < 6' + algoliasearch: '>= 4.9.1 < 6' + + '@algolia/client-abtesting@5.52.1': + resolution: {integrity: sha512-HmXOGBOAOJPounpBzBpuY0zDYeiCpxgHnQmuA7JO6ScukcBdGp3/XM9zJk5pJx/xNGD68mbPGXWpDxGtl6BwDQ==} engines: {node: '>= 14.0.0'} - '@algolia/client-analytics@5.46.0': - resolution: {integrity: sha512-AYh2uL8IUW9eZrbbT+wZElyb7QkkeV3US2NEKY7doqMlyPWE8lErNfkVN1NvZdVcY4/SVic5GDbeDz2ft8YIiQ==} + '@algolia/client-analytics@5.52.1': + resolution: {integrity: sha512-5oo4+I8iixie9vXhCyNFCzeIr8pqA3FQ//VsLHTDvZAV4ttYOPGvYHGQq5NSalrLx5Jc3dRro/5uDOlnUMcBJg==} engines: {node: '>= 14.0.0'} - '@algolia/client-common@5.46.0': - resolution: {integrity: sha512-0emZTaYOeI9WzJi0TcNd2k3SxiN6DZfdWc2x2gHt855Jl9jPUOzfVTL6gTvCCrOlT4McvpDGg5nGO+9doEjjig==} + '@algolia/client-common@5.52.1': + resolution: {integrity: sha512-qCDoZfx5MpX7XQzvQ3bC4tSEMkQWQMaF/ABtLuoze03Y/flR563CCSws02qIJ23oX7lxl92LsilZjINVyTdtLw==} engines: {node: '>= 14.0.0'} - '@algolia/client-insights@5.46.0': - resolution: {integrity: sha512-wrBJ8fE+M0TDG1As4DDmwPn2TXajrvmvAN72Qwpuv8e2JOKNohF7+JxBoF70ZLlvP1A1EiH8DBu+JpfhBbNphQ==} + '@algolia/client-insights@5.52.1': + resolution: {integrity: sha512-hnGs0/lsFJ2PWDxNBz7pxreXo/Xz7gxYRcfePBUjsH26ad0kU/sgnVZd9LwWBpsQv65z2jlb5dkyaB9WE9M9FQ==} engines: {node: '>= 14.0.0'} - '@algolia/client-personalization@5.46.0': - resolution: {integrity: sha512-LnkeX4p0ENt0DoftDJJDzQQJig/sFQmD1eQifl/iSjhUOGUIKC/7VTeXRcKtQB78naS8njUAwpzFvxy1CDDXDQ==} + '@algolia/client-personalization@5.52.1': + resolution: {integrity: sha512-2VxxNc/uBysyKvGeBdSM5n9eIDKH8kWD7wd9/yqbJAiVwU4Yv6tU1LSJusHKrXV/aCu1KW7t9Gug9QyeEmtn/Q==} engines: {node: '>= 14.0.0'} - '@algolia/client-query-suggestions@5.46.0': - resolution: {integrity: sha512-aF9tc4ex/smypXw+W3lBPB1jjKoaGHpZezTqofvDOI/oK1dR2sdTpFpK2Ru+7IRzYgwtRqHF3znmTlyoNs9dpA==} + '@algolia/client-query-suggestions@5.52.1': + resolution: {integrity: sha512-O6mPtsw3xEfNOe6gWFpYLeAZAIljNa4Hgna3bq15PwyN7nbjTY0wXJFRbzs/0YVf75Br+SbOQUmjKxXYjDiSiQ==} engines: {node: '>= 14.0.0'} - '@algolia/client-search@5.46.0': - resolution: {integrity: sha512-22SHEEVNjZfFWkFks3P6HilkR3rS7a6GjnCIqR22Zz4HNxdfT0FG+RE7efTcFVfLUkTTMQQybvaUcwMrHXYa7Q==} + '@algolia/client-search@5.52.1': + resolution: {integrity: sha512-gA8oJOV1LnQQkDf91iebNnFInHuW0gRPEgLSOQ7EfipCEjYTHm5swm1DlH9H5RaRw4RrHuzHBegnlzc0MAstcg==} engines: {node: '>= 14.0.0'} '@algolia/events@4.0.1': resolution: {integrity: sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ==} - '@algolia/ingestion@1.46.0': - resolution: {integrity: sha512-2LT0/Z+/sFwEpZLH6V17WSZ81JX2uPjgvv5eNlxgU7rPyup4NXXfuMbtCJ+6uc4RO/LQpEJd3Li59ke3wtyAsA==} + '@algolia/ingestion@1.52.1': + resolution: {integrity: sha512-U9zZfc5xIu9wRxZkt+HceJUAD4VKHKbAyLSloJdEyMRmphXeibfrY9cxqIXBcmPeZzGhn3Imb35Dq8l19PkJhw==} engines: {node: '>= 14.0.0'} - '@algolia/monitoring@1.46.0': - resolution: {integrity: sha512-uivZ9wSWZ8mz2ZU0dgDvQwvVZV8XBv6lYBXf8UtkQF3u7WeTqBPeU8ZoeTyLpf0jAXCYOvc1mAVmK0xPLuEwOQ==} + '@algolia/monitoring@1.52.1': + resolution: {integrity: sha512-a3SGNceHmkQfq77iG8Ka+w1pvwfZa/0lzEIgse30fL0kD+yKnd/dg0dQvSfFPAEt2f21DMcGkDSSeJlO3KdQjQ==} engines: {node: '>= 14.0.0'} - '@algolia/recommend@5.46.0': - resolution: {integrity: sha512-O2BB8DuySuddgOAbhyH4jsGbL+KyDGpzJRtkDZkv091OMomqIA78emhhMhX9d/nIRrzS1wNLWB/ix7Hb2eV5rg==} + '@algolia/recommend@5.52.1': + resolution: {integrity: sha512-z98QEguCFDpxb4S/PyrUK1igqF8tPsdbqOUUO6ON91vJ58w+Gwa6ncrI0oNXSFcrkxA5EqPKPQ2A1PBCn08TYQ==} engines: {node: '>= 14.0.0'} - '@algolia/requester-browser-xhr@5.46.0': - resolution: {integrity: sha512-eW6xyHCyYrJD0Kjk9Mz33gQ40LfWiEA51JJTVfJy3yeoRSw/NXhAL81Pljpa0qslTs6+LO/5DYPZddct6HvISQ==} + '@algolia/requester-browser-xhr@5.52.1': + resolution: {integrity: sha512-CI7+/0I11QeZM59Uc8whd2or0kqzFVjpaPn9Qpwll/krHcBAxk24WkAQ6WX+IwDVMfpont4YGbKwAmCre3vE8Q==} engines: {node: '>= 14.0.0'} - '@algolia/requester-fetch@5.46.0': - resolution: {integrity: sha512-Vn2+TukMGHy4PIxmdvP667tN/MhS7MPT8EEvEhS6JyFLPx3weLcxSa1F9gVvrfHWCUJhLWoMVJVB2PT8YfRGcw==} + '@algolia/requester-fetch@5.52.1': + resolution: {integrity: sha512-S6bDuw9byfOvm3T71cgdoZgrgnZq6hpdMLkx52Louh57nUAmvGQESz2aojOynQHjbTiV55smvAFbgn0qT4tJrg==} engines: {node: '>= 14.0.0'} - '@algolia/requester-node-http@5.46.0': - resolution: {integrity: sha512-xaqXyna5yBZ+r1SJ9my/DM6vfTqJg9FJgVydRJ0lnO+D5NhqGW/qaRG/iBGKr/d4fho34el6WakV7BqJvrl/HQ==} + '@algolia/requester-node-http@5.52.1': + resolution: {integrity: sha512-tqZXM+54rWo4mk5jL5Z/flE11nPmNEdXwFBM5py9DkOmbjeCNemfVd45FyM97XdzfZ0dl9uOJC6PYn1FpkeyQg==} engines: {node: '>= 14.0.0'} '@alloc/quick-lru@5.2.0': resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} - '@ampproject/remapping@2.3.0': - resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} - engines: {node: '>=6.0.0'} - '@angular-devkit/core@19.2.24': resolution: {integrity: sha512-Kd49warf6U/EyWe5BszF/eebN3zQ3bk7tgfEljAw8q/rX95UUtriJubWvp6pgzHfzBA4jwq8f+QiNZB8eBEXPA==} engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} @@ -1117,12 +1138,12 @@ packages: resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} engines: {node: '>=6.9.0'} - '@babel/compat-data@7.28.5': - resolution: {integrity: sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==} + '@babel/compat-data@7.29.3': + resolution: {integrity: sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==} engines: {node: '>=6.9.0'} - '@babel/core@7.28.5': - resolution: {integrity: sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==} + '@babel/core@7.29.0': + resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} engines: {node: '>=6.9.0'} '@babel/generator@7.29.1': @@ -1133,12 +1154,12 @@ packages: resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==} engines: {node: '>=6.9.0'} - '@babel/helper-compilation-targets@7.27.2': - resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} + '@babel/helper-compilation-targets@7.28.6': + resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} engines: {node: '>=6.9.0'} - '@babel/helper-create-class-features-plugin@7.28.5': - resolution: {integrity: sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ==} + '@babel/helper-create-class-features-plugin@7.29.3': + resolution: {integrity: sha512-RpLYy2sb51oNLjuu1iD3bwBqCBWUzjO0ocp+iaCP/lJtb2CPLcnC2Fftw+4sAzaMELGeWTgExSKADbdo0GFVzA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 @@ -1149,8 +1170,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-define-polyfill-provider@0.6.5': - resolution: {integrity: sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg==} + '@babel/helper-define-polyfill-provider@0.6.8': + resolution: {integrity: sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==} peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 @@ -1162,12 +1183,12 @@ packages: resolution: {integrity: sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==} engines: {node: '>=6.9.0'} - '@babel/helper-module-imports@7.27.1': - resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} + '@babel/helper-module-imports@7.28.6': + resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} engines: {node: '>=6.9.0'} - '@babel/helper-module-transforms@7.28.3': - resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} + '@babel/helper-module-transforms@7.28.6': + resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 @@ -1176,8 +1197,8 @@ packages: resolution: {integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==} engines: {node: '>=6.9.0'} - '@babel/helper-plugin-utils@7.27.1': - resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} + '@babel/helper-plugin-utils@7.28.6': + resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} engines: {node: '>=6.9.0'} '@babel/helper-remap-async-to-generator@7.27.1': @@ -1186,8 +1207,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-replace-supers@7.27.1': - resolution: {integrity: sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==} + '@babel/helper-replace-supers@7.28.6': + resolution: {integrity: sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 @@ -1208,12 +1229,12 @@ packages: resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} engines: {node: '>=6.9.0'} - '@babel/helper-wrap-function@7.28.3': - resolution: {integrity: sha512-zdf983tNfLZFletc0RRXYrHrucBEg95NIFMkn6K9dbeMYnsgHaSBGcQqdsCSStG2PYwRre0Qc2NNSCXbG+xc6g==} + '@babel/helper-wrap-function@7.28.6': + resolution: {integrity: sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ==} engines: {node: '>=6.9.0'} - '@babel/helpers@7.28.4': - resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==} + '@babel/helpers@7.29.2': + resolution: {integrity: sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==} engines: {node: '>=6.9.0'} '@babel/parser@7.27.0': @@ -1221,8 +1242,8 @@ packages: engines: {node: '>=6.0.0'} hasBin: true - '@babel/parser@7.29.2': - resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==} + '@babel/parser@7.29.3': + resolution: {integrity: sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==} engines: {node: '>=6.0.0'} hasBin: true @@ -1244,14 +1265,20 @@ packages: peerDependencies: '@babel/core': ^7.0.0 + '@babel/plugin-bugfix-safari-rest-destructuring-rhs-array@7.29.3': + resolution: {integrity: sha512-SRS46DFR4HqzUzCVgi90/xMoL+zeBDBvWdKYXSEzh79kXswNFEglUpMKxR04//dPqwYXWUBJ3mpUd933ru9Kmg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1': resolution: {integrity: sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.13.0 - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.3': - resolution: {integrity: sha512-b6YTX108evsvE4YgWyQ921ZAFFQm3Bn+CA3+ZXlNVnPhx+UfsVURoPjfGAPCjBgrqo30yX/C2nZGX96DxvR9Iw==} + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.6': + resolution: {integrity: sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 @@ -1267,26 +1294,26 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-import-assertions@7.27.1': - resolution: {integrity: sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==} + '@babel/plugin-syntax-import-assertions@7.28.6': + resolution: {integrity: sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-import-attributes@7.27.1': - resolution: {integrity: sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==} + '@babel/plugin-syntax-import-attributes@7.28.6': + resolution: {integrity: sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-jsx@7.27.1': - resolution: {integrity: sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==} + '@babel/plugin-syntax-jsx@7.28.6': + resolution: {integrity: sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-typescript@7.27.1': - resolution: {integrity: sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==} + '@babel/plugin-syntax-typescript@7.28.6': + resolution: {integrity: sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1303,14 +1330,14 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-async-generator-functions@7.28.0': - resolution: {integrity: sha512-BEOdvX4+M765icNPZeidyADIvQ1m1gmunXufXxvRESy/jNNyfovIqUyE7MVgGBjWktCoJlzvFA1To2O4ymIO3Q==} + '@babel/plugin-transform-async-generator-functions@7.29.0': + resolution: {integrity: sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-async-to-generator@7.27.1': - resolution: {integrity: sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA==} + '@babel/plugin-transform-async-to-generator@7.28.6': + resolution: {integrity: sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1321,32 +1348,32 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-block-scoping@7.28.5': - resolution: {integrity: sha512-45DmULpySVvmq9Pj3X9B+62Xe+DJGov27QravQJU1LLcapR6/10i+gYVAucGGJpHBp5mYxIMK4nDAT/QDLr47g==} + '@babel/plugin-transform-block-scoping@7.28.6': + resolution: {integrity: sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-class-properties@7.27.1': - resolution: {integrity: sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==} + '@babel/plugin-transform-class-properties@7.28.6': + resolution: {integrity: sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-class-static-block@7.28.3': - resolution: {integrity: sha512-LtPXlBbRoc4Njl/oh1CeD/3jC+atytbnf/UqLoqTDcEYGUPj022+rvfkbDYieUrSj3CaV4yHDByPE+T2HwfsJg==} + '@babel/plugin-transform-class-static-block@7.28.6': + resolution: {integrity: sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.12.0 - '@babel/plugin-transform-classes@7.28.4': - resolution: {integrity: sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA==} + '@babel/plugin-transform-classes@7.28.6': + resolution: {integrity: sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-computed-properties@7.27.1': - resolution: {integrity: sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw==} + '@babel/plugin-transform-computed-properties@7.28.6': + resolution: {integrity: sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1357,8 +1384,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-dotall-regex@7.27.1': - resolution: {integrity: sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw==} + '@babel/plugin-transform-dotall-regex@7.28.6': + resolution: {integrity: sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1369,8 +1396,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.27.1': - resolution: {integrity: sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ==} + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.0': + resolution: {integrity: sha512-zBPcW2lFGxdiD8PUnPwJjag2J9otbcLQzvbiOzDxpYXyCuYX9agOwMPGn1prVH0a4qzhCKu24rlH4c1f7yA8rw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 @@ -1381,14 +1408,14 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-explicit-resource-management@7.28.0': - resolution: {integrity: sha512-K8nhUcn3f6iB+P3gwCv/no7OdzOZQcKchW6N389V6PD8NUWKZHzndOd9sPDVbMoBsbmjMqlB4L9fm+fEFNVlwQ==} + '@babel/plugin-transform-explicit-resource-management@7.28.6': + resolution: {integrity: sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-exponentiation-operator@7.28.5': - resolution: {integrity: sha512-D4WIMaFtwa2NizOp+dnoFjRez/ClKiC2BqqImwKd1X28nqBtZEyCYJ2ozQrrzlxAFrcrjxo39S6khe9RNDlGzw==} + '@babel/plugin-transform-exponentiation-operator@7.28.6': + resolution: {integrity: sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1411,8 +1438,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-json-strings@7.27.1': - resolution: {integrity: sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q==} + '@babel/plugin-transform-json-strings@7.28.6': + resolution: {integrity: sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1423,8 +1450,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-logical-assignment-operators@7.28.5': - resolution: {integrity: sha512-axUuqnUTBuXyHGcJEVVh9pORaN6wC5bYfE7FGzPiaWa3syib9m7g+/IT/4VgCOe2Upef43PHzeAvcrVek6QuuA==} + '@babel/plugin-transform-logical-assignment-operators@7.28.6': + resolution: {integrity: sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1441,14 +1468,14 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-modules-commonjs@7.27.1': - resolution: {integrity: sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==} + '@babel/plugin-transform-modules-commonjs@7.28.6': + resolution: {integrity: sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-modules-systemjs@7.28.5': - resolution: {integrity: sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew==} + '@babel/plugin-transform-modules-systemjs@7.29.4': + resolution: {integrity: sha512-N7QmZ0xRZfjHOfZeQLJjwgX2zS9pdGHSVl/cjSGlo4dXMqvurfxXDMKY4RqEKzPozV78VMcd0lxyG13mlbKc4w==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1459,8 +1486,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-named-capturing-groups-regex@7.27.1': - resolution: {integrity: sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==} + '@babel/plugin-transform-named-capturing-groups-regex@7.29.0': + resolution: {integrity: sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 @@ -1471,20 +1498,20 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-nullish-coalescing-operator@7.27.1': - resolution: {integrity: sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==} + '@babel/plugin-transform-nullish-coalescing-operator@7.28.6': + resolution: {integrity: sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-numeric-separator@7.27.1': - resolution: {integrity: sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw==} + '@babel/plugin-transform-numeric-separator@7.28.6': + resolution: {integrity: sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-object-rest-spread@7.28.4': - resolution: {integrity: sha512-373KA2HQzKhQCYiRVIRr+3MjpCObqzDlyrM6u4I201wL8Mp2wHf7uB8GhDwis03k2ti8Zr65Zyyqs1xOxUF/Ew==} + '@babel/plugin-transform-object-rest-spread@7.28.6': + resolution: {integrity: sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1495,14 +1522,14 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-optional-catch-binding@7.27.1': - resolution: {integrity: sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==} + '@babel/plugin-transform-optional-catch-binding@7.28.6': + resolution: {integrity: sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-optional-chaining@7.28.5': - resolution: {integrity: sha512-N6fut9IZlPnjPwgiQkXNhb+cT8wQKFlJNqcZkWlcTqkcqx6/kU4ynGmLFoa4LViBSirn05YAwk+sQBbPfxtYzQ==} + '@babel/plugin-transform-optional-chaining@7.28.6': + resolution: {integrity: sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1513,14 +1540,14 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-private-methods@7.27.1': - resolution: {integrity: sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==} + '@babel/plugin-transform-private-methods@7.28.6': + resolution: {integrity: sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-private-property-in-object@7.27.1': - resolution: {integrity: sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ==} + '@babel/plugin-transform-private-property-in-object@7.28.6': + resolution: {integrity: sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1549,8 +1576,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-react-jsx@7.27.1': - resolution: {integrity: sha512-2KH4LWGSrJIkVf5tSiBFYuXDAoWRq2MMwgivCf+93dd0GQi8RXLjKA/0EvRnVV5G0hrHczsquXuD01L8s6dmBw==} + '@babel/plugin-transform-react-jsx@7.28.6': + resolution: {integrity: sha512-61bxqhiRfAACulXSLd/GxqmAedUSrRZIu/cbaT18T1CetkTmtDN15it7i80ru4DVqRK1WMxQhXs+Lf9kajm5Ow==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1561,14 +1588,14 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-regenerator@7.28.4': - resolution: {integrity: sha512-+ZEdQlBoRg9m2NnzvEeLgtvBMO4tkFBw5SQIUgLICgTrumLoU7lr+Oghi6km2PFj+dbUt2u1oby2w3BDO9YQnA==} + '@babel/plugin-transform-regenerator@7.29.0': + resolution: {integrity: sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-regexp-modifiers@7.27.1': - resolution: {integrity: sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA==} + '@babel/plugin-transform-regexp-modifiers@7.28.6': + resolution: {integrity: sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 @@ -1579,8 +1606,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-runtime@7.28.5': - resolution: {integrity: sha512-20NUVgOrinudkIBzQ2bNxP08YpKprUkRTiRSd2/Z5GOdPImJGkoN4Z7IQe1T5AdyKI1i5L6RBmluqdSzvaq9/w==} + '@babel/plugin-transform-runtime@7.29.0': + resolution: {integrity: sha512-jlaRT5dJtMaMCV6fAuLbsQMSwz/QkvaHOHOSXRitGGwSpR1blCY4KUKoyP2tYO8vJcqYe8cEj96cqSztv3uF9w==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1591,8 +1618,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-spread@7.27.1': - resolution: {integrity: sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q==} + '@babel/plugin-transform-spread@7.28.6': + resolution: {integrity: sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1615,8 +1642,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-typescript@7.28.5': - resolution: {integrity: sha512-x2Qa+v/CuEoX7Dr31iAfr0IhInrVOWZU/2vJMJ00FOR/2nM0BcBEclpaf9sWCDc+v5e9dMrhSH8/atq/kX7+bA==} + '@babel/plugin-transform-typescript@7.28.6': + resolution: {integrity: sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1627,8 +1654,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-unicode-property-regex@7.27.1': - resolution: {integrity: sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q==} + '@babel/plugin-transform-unicode-property-regex@7.28.6': + resolution: {integrity: sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1639,14 +1666,14 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-unicode-sets-regex@7.27.1': - resolution: {integrity: sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw==} + '@babel/plugin-transform-unicode-sets-regex@7.28.6': + resolution: {integrity: sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/preset-env@7.28.5': - resolution: {integrity: sha512-S36mOoi1Sb6Fz98fBfE+UZSpYw5mJm0NUHtIKrOuNcqeFauy1J6dIvXm2KRVKobOSaGq4t/hBXdN4HGU3wL9Wg==} + '@babel/preset-env@7.29.5': + resolution: {integrity: sha512-/69t2aEzGKHD76DyLbHysF/QH2LJOB8iFnYO37unDTKBTubzcMRv0f3H5EiN1Q6ajOd/eB7dAInF0qdFVS06kA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1668,8 +1695,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/runtime@7.29.2': - resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} + '@babel/runtime@7.29.7': + resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} engines: {node: '>=6.9.0'} '@babel/template@7.28.6': @@ -1698,26 +1725,14 @@ packages: '@borewit/text-codec@0.2.2': resolution: {integrity: sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==} - '@braintree/sanitize-url@7.1.1': - resolution: {integrity: sha512-i1L7noDNxtFyL5DmZafWy1wRVhGehQmzZaz1HiN5e7iylJMSZR7ekOV7NsIqa5qBldlLrsKv4HbgFUVlQrz8Mw==} + '@braintree/sanitize-url@7.1.2': + resolution: {integrity: sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==} - '@chevrotain/cst-dts-gen@11.0.3': - resolution: {integrity: sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==} + '@chevrotain/types@11.1.2': + resolution: {integrity: sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==} - '@chevrotain/gast@11.0.3': - resolution: {integrity: sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==} - - '@chevrotain/regexp-to-ast@11.0.3': - resolution: {integrity: sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==} - - '@chevrotain/types@11.0.3': - resolution: {integrity: sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==} - - '@chevrotain/utils@11.0.3': - resolution: {integrity: sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==} - - '@codemirror/autocomplete@6.20.1': - resolution: {integrity: sha512-1cvg3Vz1dSSToCNlJfRA2WSI4ht3K+WplO0UMOgmUYPivCyy2oueZY6Lx7M9wThm7SDUBViRmuT+OG/i8+ON9A==} + '@codemirror/autocomplete@6.20.2': + resolution: {integrity: sha512-G5FPkgIiLjOgZMjqVjvuKQ1rGPtHogLldJr33eFJdVLtmwY+giGrlv/ewljLz6b9BSQLkjxuwBc6g6omDM+YxQ==} '@codemirror/commands@6.10.3': resolution: {integrity: sha512-JFRiqhKu+bvSkDLI+rUhJwSxQxYb759W5GBezE8Uc8mHLqC9aV/9aTC7yJSqCtB3F00pylrLCwnyS91Ap5ej4Q==} @@ -1728,8 +1743,8 @@ packages: '@codemirror/language@6.12.3': resolution: {integrity: sha512-QwCZW6Tt1siP37Jet9Tb02Zs81TQt6qQrZR2H+eGMcFsL1zMrk2/b9CLC7/9ieP1fjIUMgviLWMmgiHoJrj+ZA==} - '@codemirror/lint@6.9.5': - resolution: {integrity: sha512-GElsbU9G7QT9xXhpUg1zWGmftA/7jamh+7+ydKRuT0ORpWS3wOSP0yT1FOlIZa7mIJjpVPipErsyvVqB9cfTFA==} + '@codemirror/lint@6.9.6': + resolution: {integrity: sha512-6Kp7r6XfCi/D/5sdXieMfg9pJU1bUEx96WITuLU6ESaKizCz0QHFMjY/TaFSbigDdEAIgi93itLBIUETP4oK+A==} '@codemirror/search@6.7.0': resolution: {integrity: sha512-ZvGm99wc/s2cITtMT15LFdn8aH/aS+V+DqyGq/N5ZlV5vWtH+nILvC2nw0zX7ByNoHHDZ2IxxdW38O0tc5nVHg==} @@ -1737,8 +1752,8 @@ packages: '@codemirror/state@6.6.0': resolution: {integrity: sha512-4nbvra5R5EtiCzr9BTHiTLc+MLXK2QGiAVYMyi8PkQd3SR+6ixar/Q/01Fa21TBIDOZXgeWV4WppsQolSreAPQ==} - '@codemirror/view@6.41.1': - resolution: {integrity: sha512-ToDnWKbBnke+ZLrP6vgTTDScGi5H37YYuZGniQaBzxMVdtCxMrslsmtnOvbPZk4RX9bvkQqnWR/WS/35tJA0qg==} + '@codemirror/view@6.43.0': + resolution: {integrity: sha512-V7ZCLQO3Jus9hzh2jVCCPW3mO4IBMr43O37PqSUYautJSnnJF41YlgLw21x0fLJTYvJ+Vkm6Gp+qKGH9pltgXA==} '@colors/colors@1.5.0': resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} @@ -1936,8 +1951,8 @@ packages: peerDependencies: postcss: ^8.4 - '@csstools/postcss-normalize-display-values@4.0.0': - resolution: {integrity: sha512-HlEoG0IDRoHXzXnkV4in47dzsxdsjdz6+j7MLjaACABX2NfvjFS6XVAnpaDyGesz9gK2SC7MbNwdCHusObKJ9Q==} + '@csstools/postcss-normalize-display-values@4.0.1': + resolution: {integrity: sha512-TQUGBuRvxdc7TgNSTevYqrL8oItxiwPDixk20qCB5me/W8uF7BPbhRrAvFuhEoywQp/woRsUZ6SJ+sU5idZAIA==} engines: {node: '>=18'} peerDependencies: postcss: ^8.4 @@ -1960,6 +1975,12 @@ packages: peerDependencies: postcss: ^8.4 + '@csstools/postcss-property-rule-prelude-list@1.0.0': + resolution: {integrity: sha512-IxuQjUXq19fobgmSSvUDO7fVwijDJaZMvWQugxfEUxmjBeDCVaDuMpsZ31MsTm5xbnhA+ElDi0+rQ7sQQGisFA==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + '@csstools/postcss-random-function@2.0.1': resolution: {integrity: sha512-q+FQaNiRBhnoSNo+GzqGOIBKoHQ43lYz0ICrV+UudfWnEF6ksS6DsBIJSISKQT2Bvu3g4k6r7t0zYrk5pDlo8w==} engines: {node: '>=18'} @@ -1990,6 +2011,12 @@ packages: peerDependencies: postcss: ^8.4 + '@csstools/postcss-syntax-descriptor-syntax-production@1.0.1': + resolution: {integrity: sha512-GneqQWefjM//f4hJ/Kbox0C6f2T7+pi4/fqTqOFGTL3EjnvOReTqO1qUQ30CaUjkwjYq9qZ41hzarrAxCc4gow==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + '@csstools/postcss-system-ui-font-family@1.0.0': resolution: {integrity: sha512-s3xdBvfWYfoPSBsikDXbuorcMG1nN1M6GdU0qBsGfcmNR0A/qhloQZpTxjA3Xsyrk1VJvwb2pOfiOT3at/DuIQ==} engines: {node: '>=18'} @@ -2036,8 +2063,8 @@ packages: resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==} engines: {node: '>=10.0.0'} - '@docsearch/core@4.3.1': - resolution: {integrity: sha512-ktVbkePE+2h9RwqCUMbWXOoebFyDOxHqImAqfs+lC8yOU+XwEW4jgvHGJK079deTeHtdhUNj0PXHSnhJINvHzQ==} + '@docsearch/core@4.6.3': + resolution: {integrity: sha512-rUOujwIpxJRgD7+kicVsI3D5sqBvdiRTquzWBpTEXZs8ZXfGbfzpus5HqumaNYTppN2HvH8E2yNuRwYdHJeOlA==} peerDependencies: '@types/react': '>= 16.8.0 < 20.0.0' react: '>= 16.8.0 < 20.0.0' @@ -2050,11 +2077,11 @@ packages: react-dom: optional: true - '@docsearch/css@4.3.2': - resolution: {integrity: sha512-K3Yhay9MgkBjJJ0WEL5MxnACModX9xuNt3UlQQkDEDZJZ0+aeWKtOkxHNndMRkMBnHdYvQjxkm6mdlneOtU1IQ==} + '@docsearch/css@4.6.3': + resolution: {integrity: sha512-nlOwcXcsNAptQl4vlL4MA78qNJKO0Qlds5GuBjCoePgkebTXLSf8Qt1oyZ3YBshYupKXG9VRGEsk1zr23d+bzQ==} - '@docsearch/react@4.3.2': - resolution: {integrity: sha512-74SFD6WluwvgsOPqifYOviEEVwDxslxfhakTlra+JviaNcs7KK/rjsPj89kVEoQc9FUxRkAofaJnHIR7pb4TSQ==} + '@docsearch/react@4.6.3': + resolution: {integrity: sha512-Bg2wdDsoQVlNCcEKuEJAU04tvHCqgx8rIu+uIoM4pRtcx3TBKJuXutJik3LTA8LRc9YEyHkrYUrmcC0D7BYf+g==} peerDependencies: '@types/react': '>= 16.8.0 < 20.0.0' react: '>= 16.8.0 < 20.0.0' @@ -2070,12 +2097,12 @@ packages: search-insights: optional: true - '@docusaurus/babel@3.10.0': - resolution: {integrity: sha512-mqCJhCZNZUDg0zgDEaPTM4DnRsisa24HdqTy/qn/MQlbwhTb4WVaZg6ZyX6yIVKqTz8fS1hBMgM+98z+BeJJDg==} + '@docusaurus/babel@3.10.1': + resolution: {integrity: sha512-DZzFO1K3v/GoEt1fx1DiYHF4en+PuhtQf1AkQJa5zu3CoeKSpr5cpQRUlz3jr0m44wyzmSXu9bVpfir+N4+8bg==} engines: {node: '>=20.0'} - '@docusaurus/bundler@3.10.0': - resolution: {integrity: sha512-iONUGZGgp+lAkw/cJZH6irONcF4p8+278IsdRlq8lYhxGjkoNUs0w7F4gVXBYSNChq5KG5/JleTSsdJySShxow==} + '@docusaurus/bundler@3.10.1': + resolution: {integrity: sha512-HIqQPvbqnnQRe4NsBd1774KRarjXqS6wHsWELtyuSs1gCfvixJO2jUGH/OEBtr1Gvzpw+ze5CjGMvSJ8UE1KUw==} engines: {node: '>=20.0'} peerDependencies: '@docusaurus/faster': '*' @@ -2083,8 +2110,8 @@ packages: '@docusaurus/faster': optional: true - '@docusaurus/core@3.10.0': - resolution: {integrity: sha512-mgLdQsO8xppnQZc3LPi+Mf+PkPeyxJeIx11AXAq/14fsaMefInQiMEZUUmrc7J+956G/f7MwE7tn8KZgi3iRcA==} + '@docusaurus/core@3.10.1': + resolution: {integrity: sha512-3pf2fXXw0eVk8WnC3T4LIigRDupcpvngpKo9Vy7mYyBhuddc0klDUuZAIfzMoK6z05pdlk6EFC/vBSX43+1O5w==} engines: {node: '>=20.0'} hasBin: true peerDependencies: @@ -2096,97 +2123,97 @@ packages: '@docusaurus/faster': optional: true - '@docusaurus/cssnano-preset@3.10.0': - resolution: {integrity: sha512-qzSshTO1DB3TYW+dPUal5KHM7XPc5YQfzF3Kdb2NDACJUyGbNcFtw3tGkCJlYwhNCRKbZcmwraKUS1i5dcHdGg==} + '@docusaurus/cssnano-preset@3.10.1': + resolution: {integrity: sha512-eNfHGcTKCSq6xmcavAkX3RRclHaE2xRCMParlDXLdXVP01/a2e/jKXMj/0ULnLFQSNwwuI62L0Ge8J+nZsR7UQ==} engines: {node: '>=20.0'} - '@docusaurus/logger@3.10.0': - resolution: {integrity: sha512-9jrZzFuBH1LDRlZ7cznAhCLmAZ3HSDqgwdrSSZdGHq9SPUOQgXXu8mnxe2ZRB9NS1PCpMTIOVUqDtZPIhMafZg==} + '@docusaurus/logger@3.10.1': + resolution: {integrity: sha512-oPjNFnfJsRCkePVjkGrxWGq4MvJKRQT0r9jOP0eRBTZ7Wr9FAbzdP/Gjs0I2Ss6YRkPoEgygKG112OkE6skvJw==} engines: {node: '>=20.0'} - '@docusaurus/mdx-loader@3.10.0': - resolution: {integrity: sha512-mQQV97080AH4PYNs087l202NMDqRopZA4mg5W76ZZyTFrmWhJ3mHg+8A+drJVENxw5/Q+wHMHLgsx+9z1nEs0A==} + '@docusaurus/mdx-loader@3.10.1': + resolution: {integrity: sha512-GRmeb/wQ+iXRrFwcHBfgQhrJxGElgCsoTWZYDhccjsZVne1p8MK/EpQVIloXttz76TCe78kKD5AEG9n1xc1oxQ==} engines: {node: '>=20.0'} peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/module-type-aliases@3.10.0': - resolution: {integrity: sha512-/1O0Zg8w3DFrYX/I6Fbss7OJrtZw1QoyjDhegiFNHVi9A9Y0gQ3jUAytVxF6ywpAWpLyLxch8nN8H/V3XfzdJQ==} + '@docusaurus/module-type-aliases@3.10.1': + resolution: {integrity: sha512-YoOZKUdGlp8xSYhuAkGdSo5Ydkbq4V4eK3sD8v0a2hloxCWdQbNBhkc+Ko9QyjpESc0BYcIGM5iHVAy5hdFV6w==} peerDependencies: react: '*' react-dom: '*' - '@docusaurus/plugin-content-blog@3.10.0': - resolution: {integrity: sha512-RuTz68DhB7CL96QO5UsFbciD7GPYq6QV+YMfF9V0+N4ZgLhJIBgpVAr8GobrKF6NRe5cyWWETU5z5T834piG9g==} + '@docusaurus/plugin-content-blog@3.10.1': + resolution: {integrity: sha512-mmkgE6Q2+K74tnkou7tXlpDLvoCU/qkSa2GSQ3XUiHWvcebCoDQzS670RR3tO8PmaWlIyWWISYWzZLuMfxunRA==} engines: {node: '>=20.0'} peerDependencies: '@docusaurus/plugin-content-docs': '*' react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/plugin-content-docs@3.10.0': - resolution: {integrity: sha512-9BjHhf15ct8Z7TThTC0xRndKDVvMKmVsAGAN7W9FpNRzfMdScOGcXtLmcCWtJGvAezjOJIm6CxOYCy3Io5+RnQ==} + '@docusaurus/plugin-content-docs@3.10.1': + resolution: {integrity: sha512-2jRVrtzjf8LClGTHQlwlwuD3wQXRx3WEoF7XUarJ8Ou+0onV+SLtejsyfY9JLpfUh9hPhXM4pbBGkyAY4Bi3HQ==} engines: {node: '>=20.0'} peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/plugin-content-pages@3.10.0': - resolution: {integrity: sha512-5amX8kEJI+nIGtuLVjYk59Y5utEJ3CHETFOPEE4cooIRLA4xM4iBsA6zFgu4ljcopeYwvBzFEWf5g2I6Yb9SkA==} + '@docusaurus/plugin-content-pages@3.10.1': + resolution: {integrity: sha512-huJpaRPMl42nsFwuCXvV8bVDj2MazuwRJIUylI/RSlmZeJssVoZXeCjVf1y+1Drtpa9SKcdGn8yoJ76IRJijtw==} engines: {node: '>=20.0'} peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/plugin-css-cascade-layers@3.10.0': - resolution: {integrity: sha512-6q1vtt5FJcg5osgkHeM1euErECNqEZ5Z1j69yiNx2luEBIso+nxCkS9nqj8w+MK5X7rvKEToGhFfOFWncs51pQ==} + '@docusaurus/plugin-css-cascade-layers@3.10.1': + resolution: {integrity: sha512-r//fn+MNHkE1wCof8T29VAQezt1enGCpsFxoziBbvLgBM4JfXN2P3rxrBaavHmvLvm7lYkpJeitcDthwnmWCTw==} engines: {node: '>=20.0'} - '@docusaurus/plugin-debug@3.10.0': - resolution: {integrity: sha512-XcljKN+G+nmmK69uQA1d9BlYU3ZftG3T3zpK8/7Hf/wrOlV7TA4Ampdrdwkg0jElKdKAoSnPhCO0/U3bQGsVQQ==} + '@docusaurus/plugin-debug@3.10.1': + resolution: {integrity: sha512-9KqOpKNfAyqGZykRb9LhIT/vyRF6sm/ykhjj/39JvaJahDS+jZJE0Z1Wfz9q3DUNDTMNN0Q7u/kk4rKKU+IJuA==} engines: {node: '>=20.0'} peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/plugin-google-analytics@3.10.0': - resolution: {integrity: sha512-hTEoodatpBZnUat5nFExbuTGA1lhWGy7vZGuTew5Q3QDtGKFpSJLYmZJhdTjvCFwv1+qQ67hgAVlKdJOB8TXow==} + '@docusaurus/plugin-google-analytics@3.10.1': + resolution: {integrity: sha512-8o0P1KtmgdYQHH+oInitPpRWI0Of5XednAX4+DMhQNSmGSRNrsEEHg1ebv35m9AgRClfAytCJ5jA9KvcASTyuA==} engines: {node: '>=20.0'} peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/plugin-google-gtag@3.10.0': - resolution: {integrity: sha512-iB/Zzjv/eelJRbdULZqzWCbgMgJ7ht4ONVjXtN3+BI/muil6S87gQ1OJyPwlXD+ELdKkitC7bWv5eJdYOZLhrQ==} + '@docusaurus/plugin-google-gtag@3.10.1': + resolution: {integrity: sha512-pu3xIUo5o/zCMLfUY9BO5KOwSH0zIsAGyFRPvXHayFSA5XIhCU/SFuB0g0ZNjFn9niZLCaNvoeAuOGFJZq0fdw==} engines: {node: '>=20.0'} peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/plugin-google-tag-manager@3.10.0': - resolution: {integrity: sha512-FEjZxqKgLHa+Wez/EgKxRwvArNCWIScfyEQD95rot7jkxp6nonjI5XIbGfO/iYhM5Qinwe8aIEQHP2KZtpqVuA==} + '@docusaurus/plugin-google-tag-manager@3.10.1': + resolution: {integrity: sha512-f6fyGHiCm7kJHBtAisGQS5oNBnpnMTYQZxDXeVrnw/3zWU+LMA22pr6UHGYkBKDbN+qPC5QHG3NuOfzQLq3+Lw==} engines: {node: '>=20.0'} peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/plugin-sitemap@3.10.0': - resolution: {integrity: sha512-DVTSLjB97hIjmayGnGcBfognCeI7ZuUKgEnU7Oz81JYqXtVg94mVTthDjq3QHTylYNeCUbkaW8VF0FDLcc8pPw==} + '@docusaurus/plugin-sitemap@3.10.1': + resolution: {integrity: sha512-C26MbmmqgdjkDq1htaZ3aD7LzEDKFWXfpyQpt0EOUThuq5nV77zDaedV20yHcVo9p+3ey9aZ4pbHA0D3QcZTzg==} engines: {node: '>=20.0'} peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/plugin-svgr@3.10.0': - resolution: {integrity: sha512-lNljBESaETZqVBMPqkrGchr+UPT1eZzEPLmJhz8I76BxbjqgsUnRvrq6lQJ9sYjgmgX52KB7kkgczqd2yzoswQ==} + '@docusaurus/plugin-svgr@3.10.1': + resolution: {integrity: sha512-6SFxsmjWFkVLDmBUvFK6i72QjUwqyQFe4Ovz+SUJophJjOyVG3ZZG5IQpBC/kX/Gfv1yWeU9nWauH6F6Q7QX/Q==} engines: {node: '>=20.0'} peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/preset-classic@3.10.0': - resolution: {integrity: sha512-kw/Ye02Hc6xP1OdTswy8yxQEHg0fdPpyWAQRxr5b2x3h7LlG2Zgbb5BDFROnXDDMpUxB7YejlocJIE5HIEfpNA==} + '@docusaurus/preset-classic@3.10.1': + resolution: {integrity: sha512-YO/FL8v1zmbxoTso6mjMz/RDjhaTJxb1UpFFTDdY5847LLDCeyYiYlrhyTbgN1RIN3xnkLKZ9Lj1x8hUzI4JOg==} engines: {node: '>=20.0'} peerDependencies: react: ^18.0.0 || ^19.0.0 @@ -2197,23 +2224,23 @@ packages: peerDependencies: react: '*' - '@docusaurus/theme-classic@3.10.0': - resolution: {integrity: sha512-9msCAsRdN+UG+RwPwCFb0uKy4tGoPh5YfBozXeGUtIeAgsMdn6f3G/oY861luZ3t8S2ET8S9Y/1GnpJAGWytww==} + '@docusaurus/theme-classic@3.10.1': + resolution: {integrity: sha512-VU1RK0qb2pab0si4r7HFK37cYco8VzqLj3u1PspVipSr/z/GPVKHO4/HXbnePqHoWDk8urjyGSeatH0NIMBM1A==} engines: {node: '>=20.0'} peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/theme-common@3.10.0': - resolution: {integrity: sha512-Dkp1YXKn16ByCJAdIjbDIOpVb4Z66MsVD694/ilX1vAAHaVEMrVsf/NPd9VgreyFx08rJ9GqV1MtzsbTcU73Kg==} + '@docusaurus/theme-common@3.10.1': + resolution: {integrity: sha512-0YtmIeoNo1fIw65LO8+/1dPgmDV86UmhMkow37gzjytuiCSQm9xob6PJy0L4kuQEMTLfUOGvkXvZr7GPrHquMA==} engines: {node: '>=20.0'} peerDependencies: '@docusaurus/plugin-content-docs': '*' react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/theme-mermaid@3.10.0': - resolution: {integrity: sha512-Y2xrlwhIJ80oOZIO3PXL6A7J869splfcMI87E3NKpYsy3zJxOyV+BP1QMtGi59ajKgU868HPuyyn6J+6BZGOBg==} + '@docusaurus/theme-mermaid@3.10.1': + resolution: {integrity: sha512-2gxpmln8Pc4EN1oWzshQEx2HTs67jk14v7MmgqGs8ZU7Nm8oihg+fTouof2u4vN8DtB3Fln4cDJu4UprSX1S3Q==} engines: {node: '>=20.0'} peerDependencies: '@mermaid-js/layout-elk': ^0.1.9 @@ -2223,36 +2250,36 @@ packages: '@mermaid-js/layout-elk': optional: true - '@docusaurus/theme-search-algolia@3.10.0': - resolution: {integrity: sha512-f5FPKI08e3JRG63vR/o4qeuUVHUHzFzM0nnF+AkB67soAZgNsKJRf2qmUZvlQkGwlV+QFkKe4D0ANMh1jToU3g==} + '@docusaurus/theme-search-algolia@3.10.1': + resolution: {integrity: sha512-OTaARARVZj2GvkJQjB+1jOIxntRaXea+G+fMsNqrZBAU1O1vJKDW22R7kECOHW27oJCLFN9HKaZeRrfAUyviug==} engines: {node: '>=20.0'} peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/theme-translations@3.10.0': - resolution: {integrity: sha512-L9IbFLwTc5+XdgH45iQYufLn0SVZd6BUNelDbKIFlH+E4hhjuj/XHWAFMX/w2K59rfy8wak9McOaei7BSUfRPA==} + '@docusaurus/theme-translations@3.10.1': + resolution: {integrity: sha512-cLMyaKivjBVWKMJuWqyFVVgtqe8DPJNPkog0bn8W1MDVAKcPdxRFycBfC1We1RaNp7Rdk513bmtW78RR6OBxBw==} engines: {node: '>=20.0'} - '@docusaurus/tsconfig@3.10.0': - resolution: {integrity: sha512-TXdC3WXuPrdQAexLvjUJfnYf3YKEgEqAs5nK0Q88pRBCW7t7oN4ILvWYb3A5Z1wlSXyXGWW/mCUmLEhdWsjnDQ==} + '@docusaurus/tsconfig@3.10.1': + resolution: {integrity: sha512-rYvB7yqkdqWIpAbDzQljGfM4cDBkLTbhmagZBEcsyj6oPUsz47lmW2pYdN1j+7sGFgltbAmQH62xfbrij4Eh6Q==} - '@docusaurus/types@3.10.0': - resolution: {integrity: sha512-F0dOt3FOoO20rRaFK7whGFQZ3ggyrWEdQc/c8/UiRuzhtg4y1w9FspXH5zpCT07uMnJKBPGh+qNazbNlCQqvSw==} + '@docusaurus/types@3.10.1': + resolution: {integrity: sha512-XYMK8k1szDCFMw2V+Xyen0g7Kee1sP3dtFnl7vkGkZOkeAJ/oPDQPL8iz4HBKOo/cwU8QeV6onVjMqtP+tFzsw==} peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/utils-common@3.10.0': - resolution: {integrity: sha512-JyL7sb9QVDgYvudIS81Dv0lsWm7le0vGZSDwsztxWam1SPBqrnkvBy9UYL/amh6pbybkyYTd3CMTkO24oMlCSw==} + '@docusaurus/utils-common@3.10.1': + resolution: {integrity: sha512-5mFSgEADtnFxFH7RLw02QA5MpU5JVUCj0MPeIvi/aF4Fi45tQRIuTwXoXDqJ+1VfQJuYJGz3SI63wmGz4HvXzA==} engines: {node: '>=20.0'} - '@docusaurus/utils-validation@3.10.0': - resolution: {integrity: sha512-c+6n2+ZPOJtWWc8Bb/EYdpSDfjYEScdCu9fB/SNjOmSCf1IdVnGf2T53o0tsz0gDRtCL90tifTL0JE/oMuP1Mw==} + '@docusaurus/utils-validation@3.10.1': + resolution: {integrity: sha512-cRv1X69jwaWv47waglllgZVWzeBFLhl53XT/XED/83BerVBTC5FTP8WTcVl8Z6sZOegDSwitu/wpCSPCDOT6lg==} engines: {node: '>=20.0'} - '@docusaurus/utils@3.10.0': - resolution: {integrity: sha512-T3B0WTigsIthe0D4LQa2k+7bJY+c3WS+Wq2JhcznOSpn1lSN64yNtHQXboCj3QnUs1EuAZszQG1SHKu5w5ZrlA==} + '@docusaurus/utils@3.10.1': + resolution: {integrity: sha512-3ojeJry9xBYdJO6qoyyzqeJFSJBVx2mXhyDzSdjwL2+URFQMf+h25gG38iswGImicK0ELjTd1EL2xzk8hf3QPw==} engines: {node: '>=20.0'} '@emnapi/core@1.10.0': @@ -2276,6 +2303,12 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/aix-ppc64@0.27.7': + resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/aix-ppc64@0.28.0': resolution: {integrity: sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==} engines: {node: '>=18'} @@ -2294,6 +2327,12 @@ packages: cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.27.7': + resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm64@0.28.0': resolution: {integrity: sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==} engines: {node: '>=18'} @@ -2312,6 +2351,12 @@ packages: cpu: [arm] os: [android] + '@esbuild/android-arm@0.27.7': + resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-arm@0.28.0': resolution: {integrity: sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==} engines: {node: '>=18'} @@ -2330,6 +2375,12 @@ packages: cpu: [x64] os: [android] + '@esbuild/android-x64@0.27.7': + resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/android-x64@0.28.0': resolution: {integrity: sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==} engines: {node: '>=18'} @@ -2348,6 +2399,12 @@ packages: cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.27.7': + resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-arm64@0.28.0': resolution: {integrity: sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==} engines: {node: '>=18'} @@ -2366,6 +2423,12 @@ packages: cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.27.7': + resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/darwin-x64@0.28.0': resolution: {integrity: sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==} engines: {node: '>=18'} @@ -2384,6 +2447,12 @@ packages: cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.27.7': + resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-arm64@0.28.0': resolution: {integrity: sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==} engines: {node: '>=18'} @@ -2402,6 +2471,12 @@ packages: cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.27.7': + resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/freebsd-x64@0.28.0': resolution: {integrity: sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==} engines: {node: '>=18'} @@ -2420,6 +2495,12 @@ packages: cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.27.7': + resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm64@0.28.0': resolution: {integrity: sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==} engines: {node: '>=18'} @@ -2438,6 +2519,12 @@ packages: cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.27.7': + resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-arm@0.28.0': resolution: {integrity: sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==} engines: {node: '>=18'} @@ -2456,6 +2543,12 @@ packages: cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.27.7': + resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-ia32@0.28.0': resolution: {integrity: sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==} engines: {node: '>=18'} @@ -2474,6 +2567,12 @@ packages: cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.27.7': + resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-loong64@0.28.0': resolution: {integrity: sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==} engines: {node: '>=18'} @@ -2492,6 +2591,12 @@ packages: cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.27.7': + resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-mips64el@0.28.0': resolution: {integrity: sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==} engines: {node: '>=18'} @@ -2510,6 +2615,12 @@ packages: cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.27.7': + resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-ppc64@0.28.0': resolution: {integrity: sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==} engines: {node: '>=18'} @@ -2528,6 +2639,12 @@ packages: cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.27.7': + resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-riscv64@0.28.0': resolution: {integrity: sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==} engines: {node: '>=18'} @@ -2546,6 +2663,12 @@ packages: cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.27.7': + resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-s390x@0.28.0': resolution: {integrity: sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==} engines: {node: '>=18'} @@ -2564,6 +2687,12 @@ packages: cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.27.7': + resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + '@esbuild/linux-x64@0.28.0': resolution: {integrity: sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==} engines: {node: '>=18'} @@ -2576,6 +2705,12 @@ packages: cpu: [arm64] os: [netbsd] + '@esbuild/netbsd-arm64@0.27.7': + resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-arm64@0.28.0': resolution: {integrity: sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==} engines: {node: '>=18'} @@ -2594,6 +2729,12 @@ packages: cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.27.7': + resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + '@esbuild/netbsd-x64@0.28.0': resolution: {integrity: sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==} engines: {node: '>=18'} @@ -2606,6 +2747,12 @@ packages: cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-arm64@0.27.7': + resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-arm64@0.28.0': resolution: {integrity: sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==} engines: {node: '>=18'} @@ -2624,6 +2771,12 @@ packages: cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.27.7': + resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + '@esbuild/openbsd-x64@0.28.0': resolution: {integrity: sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==} engines: {node: '>=18'} @@ -2636,6 +2789,12 @@ packages: cpu: [arm64] os: [openharmony] + '@esbuild/openharmony-arm64@0.27.7': + resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + '@esbuild/openharmony-arm64@0.28.0': resolution: {integrity: sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==} engines: {node: '>=18'} @@ -2654,6 +2813,12 @@ packages: cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.27.7': + resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/sunos-x64@0.28.0': resolution: {integrity: sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==} engines: {node: '>=18'} @@ -2672,6 +2837,12 @@ packages: cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.27.7': + resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-arm64@0.28.0': resolution: {integrity: sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==} engines: {node: '>=18'} @@ -2690,6 +2861,12 @@ packages: cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.27.7': + resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-ia32@0.28.0': resolution: {integrity: sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==} engines: {node: '>=18'} @@ -2708,6 +2885,12 @@ packages: cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.27.7': + resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@esbuild/win32-x64@0.28.0': resolution: {integrity: sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==} engines: {node: '>=18'} @@ -2728,16 +2911,16 @@ packages: resolution: {integrity: sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/config-helpers@0.5.5': - resolution: {integrity: sha512-eIJYKTCECbP/nsKaaruF6LW967mtbQbsw4JTtSVkUQc9MneSkbrgPJAbKl9nWr0ZeowV8BfsarBmPpBzGelA2w==} + '@eslint/config-helpers@0.6.0': + resolution: {integrity: sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} '@eslint/core@1.2.1': resolution: {integrity: sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/css-tree@4.0.2': - resolution: {integrity: sha512-eqSkC3mka2tiqOuPZKqvxNJoRzpxMss3Np3Yqi4sW7nTTRCpTKB2hzrY4JRsi0ZP3QbVfp23sgEm7VCoOjesmw==} + '@eslint/css-tree@4.0.3': + resolution: {integrity: sha512-lMgQJ5zg4YwsGPWtKS7Rc5Z4xqc2B9iOTtmDvhjMRa8GJ8/9zoKRtz26D7gCtWquy0J7oyeOJBwRP5M8slM/4w==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} '@eslint/js@10.0.1': @@ -2787,20 +2970,20 @@ packages: '@formatjs/fast-memoize@2.2.7': resolution: {integrity: sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ==} - '@formatjs/fast-memoize@3.1.2': - resolution: {integrity: sha512-vPnriihkfK0lzoQGaXq+qXH23VsYyansRTkTgo2aTG0k1NjLFyZimFVdfj4C9JkSE5dm7CEngcQ5TTc1yAyBfQ==} + '@formatjs/fast-memoize@3.1.5': + resolution: {integrity: sha512-KLi3fan6WnCHmigd9pmEEN8Hid0v4wiFBW576M/d07KMWYecf1CvyMI3n34vCmHT4AoVqG2n702kiHbXjzZX2A==} '@formatjs/icu-messageformat-parser@2.11.4': resolution: {integrity: sha512-7kR78cRrPNB4fjGFZg3Rmj5aah8rQj9KPzuLsmcSn4ipLXQvC04keycTI1F7kJYDwIXtT2+7IDEto842CfZBtw==} - '@formatjs/icu-messageformat-parser@3.5.4': - resolution: {integrity: sha512-JVY39ROgLt+pIYngo6piyj4OVfZmXs/2FkC4wLS+ql1Eig/sGJKB7YwDO/5bkJFkfwaFAeIpgEiJc8hiYxNalw==} + '@formatjs/icu-messageformat-parser@3.5.9': + resolution: {integrity: sha512-PZm6O9JI/gUPtQV9r2eaMuLb4yWqV2vz+ot03ORHWTKO343LSpZi0TqeXLB2ZZGDXLCw2SbfgsQ0GxoxXMl79g==} '@formatjs/icu-skeleton-parser@1.8.16': resolution: {integrity: sha512-H13E9Xl+PxBd8D5/6TVUluSpxGNvFSlN/b3coUp0e0JpuWXXnQDiavIpY3NnvSp4xhEMoXyyBvVfdFX8jglOHQ==} - '@formatjs/icu-skeleton-parser@2.1.4': - resolution: {integrity: sha512-8bSFZbrlvGX11ywMZxtgkPBt5Q8/etyts7j7j+GWpOVK1g43zwMIH3LZxk43HAtEP7L/jtZ+OZaMiFTOiBj9CA==} + '@formatjs/icu-skeleton-parser@2.1.9': + resolution: {integrity: sha512-rsxswgHMfU1zUgB2byc08fesf83wLGjFnzLCEtuf00mx2doiqc6pYrf67raI37XqdRcGUviQepk2UKGqpng74Q==} '@formatjs/intl-localematcher@0.6.2': resolution: {integrity: sha512-XOMO2Hupl0wdd172Y06h6kLpBz6Dv+J4okPLl4LPtzbr8f66WbIoy4ev98EBuZ6ZK4h5ydTN6XneT4QVpD7cdA==} @@ -2832,8 +3015,8 @@ packages: engines: {node: '>=6'} hasBin: true - '@grpc/proto-loader@0.8.0': - resolution: {integrity: sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==} + '@grpc/proto-loader@0.8.1': + resolution: {integrity: sha512-wtF6h+DY6M3YaDBPAmvuuA6jV8Sif9MjtOI5euKFWRgCDl5PeDpPsHR9u2l6St5ceY8AZgoNDww5+HvEsXFsGg==} engines: {node: '>=6'} hasBin: true @@ -2866,11 +3049,11 @@ packages: '@iconify/types@2.0.0': resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} - '@iconify/utils@3.1.0': - resolution: {integrity: sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw==} + '@iconify/utils@3.1.3': + resolution: {integrity: sha512-LPKOXPn/zV+zis1oOfGWogaXVpqUybF3ZS6SCZIsz8vg0ivVp9+fVqyYB7xq0aiST/VhUQYGO1qo6uoYSiEJqw==} - '@img/colour@1.0.0': - resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==} + '@img/colour@1.1.0': + resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} engines: {node: '>=18'} '@img/sharp-darwin-arm64@0.34.5': @@ -3029,14 +3212,10 @@ packages: resolution: {integrity: sha512-O1SJ+BbeFVsUTF4af1MfagJZM+lPgLjI8lQ3SZNjpo8SGJReSbUl2ii03OKuGni/G0yp2GnRLpOTNSHYGtVrcg==} hasBin: true - '@immich/svelte-markdown-preprocess@0.4.1': - resolution: {integrity: sha512-/N5dhu3fnRZUoZ+Z9hrIV61o9wi6Uf70TDxqiinXNYlXfqP81p1o77Z5mhbxtNigTNcp6GwpGeHAXRHQrU9JAQ==} - peerDependencies: - svelte: ^5.0.0 - - '@immich/ui@0.76.2': - resolution: {integrity: sha512-D5oqBMyGg8x7YcrmWLgYO1z6d5BU454jejoDJqkW/oJGHMXCSSyY+l/skmVR+fLd1Pttf28gJE9TVG1xXqJ0rA==} + '@immich/ui@0.79.3': + resolution: {integrity: sha512-QO2gLcmAIVLvcv3eb6RZ5kDahmVMDZlfE2RfbpyRKNI1wTjvTLP5ibIsofY0fXxLf44cN3vcjmz16CXS8bvVWQ==} peerDependencies: + '@sveltejs/kit': ^2.13.0 svelte: ^5.0.0 '@inquirer/ansi@1.0.2': @@ -3182,8 +3361,8 @@ packages: '@types/node': optional: true - '@internationalized/date@3.12.1': - resolution: {integrity: sha512-6IedsVWXyq4P9Tj+TxuU8WGWM70hYLl12nbYU8jkikVpa6WXapFazPUcHUMDMoWftIDE2ILDkFFte6W2nFCkRQ==} + '@internationalized/date@3.12.2': + resolution: {integrity: sha512-FY1Y+H64NDs+HAF6omlnWxm3mEpfgaCSWtL5l551ZZfImA+kGjPFgrnJrGjH6lfmLL0g8Z/mBu1R3kufeCp6Jw==} '@ioredis/commands@1.5.1': resolution: {integrity: sha512-JH8ZL/ywcJyR9MmJ5BNqZllXNZQqQbnVZOqpPQqE1vHiFgAw4NHbvE0FOduNU8IX9babitBT46571OnPTT0Zcw==} @@ -3196,10 +3375,6 @@ packages: resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} engines: {node: '>=18.0.0'} - '@istanbuljs/schema@0.1.6': - resolution: {integrity: sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==} - engines: {node: '>=8'} - '@jest/schemas@29.6.3': resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -3370,8 +3545,8 @@ packages: resolution: {integrity: sha512-x/iUDjcS90W69PryLDIMgFyV21YLTnG9zOpPXS7Bkt2b8AsY3zZsIpOLBkYr9fBcF3HbkKaER5hOBZLfpLgYNw==} engines: {node: '>= 14.0.0'} - '@koa/router@15.4.0': - resolution: {integrity: sha512-vKYlXtoCfcAN8z4dHiveYX55rTYOgHEYJNumK1WM9ZAwaArhreGVkyC1LTMGfUQUJyIO/SbwRFBOHeOCY8/MaQ==} + '@koa/router@15.5.0': + resolution: {integrity: sha512-KSC0oG/5t6ITu5wqX4lJseA/dngoj14hEaohrLZEXtlUT2RRyJvwaJ0KV+5uQoaWrY3A8ClHOrBEU4g8dujn8Q==} engines: {node: '>= 20'} peerDependencies: koa: ^2.0.0 || ^3.0.0 @@ -3415,8 +3590,8 @@ packages: '@mapbox/point-geometry@1.1.0': resolution: {integrity: sha512-YGcBz1cg4ATXDCM/71L9xveh4dynfGmcLDqufR+nQQy3fKwsAZsWd/x4621/6uJaeB9mwOHE6hPeDgXz9uViUQ==} - '@mapbox/tiny-sdf@2.1.0': - resolution: {integrity: sha512-uFJhNh36BR4OCuWIEiWaEix9CA2WzT6CAIcqVjWYpnx8+QDtS+oC4QehRrx5cX4mgWs37MmKnwUejeHxVymzNg==} + '@mapbox/tiny-sdf@2.2.0': + resolution: {integrity: sha512-LVL4wgI9YAum5V+LNVQO6QgFBPw7/MIIY4XJPNsPDMrjEwcE+JfKk1LuIl8GnF197ejVdC9QdPaxrx5gfgdGXg==} '@mapbox/unitbezier@0.0.1': resolution: {integrity: sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw==} @@ -3434,8 +3609,8 @@ packages: '@maplibre/geojson-vt@6.1.0': resolution: {integrity: sha512-2eIY4gZxeKIVOZVNkAMb+5NgXhgsMQpOveTQAvnp53LYqHGJZDidk7Ew0Tged9PThidpbS+NFTh0g4zivhPDzQ==} - '@maplibre/maplibre-gl-style-spec@24.8.1': - resolution: {integrity: sha512-zxa92qF96ZNojLxeAjnaRpjVCy+swoUNJvDhtpC90k7u5F0TMr4GmvNqMKvYrMoPB8d7gRSXbMG1hBbmgESIsw==} + '@maplibre/maplibre-gl-style-spec@24.8.5': + resolution: {integrity: sha512-EzEJmMt6thioRH7GI9LWS7ahXTcAhAPGWCe6oTP2Ps4YnsXOOAfeqx854lZaiDnwURfHmcCKV1mr6oo0i23x6w==} hasBin: true '@maplibre/mlt@1.1.9': @@ -3468,8 +3643,8 @@ packages: '@types/react': '>=16' react: '>=16' - '@mermaid-js/parser@0.6.3': - resolution: {integrity: sha512-lnjOhe7zyHjc+If7yT4zoedx2vo4sHaTmtkl1+or8BRTnCtDmcTpAjpzDSfCZrshM5bCoz0GyidzadJAH1xobA==} + '@mermaid-js/parser@1.1.1': + resolution: {integrity: sha512-VuHdsYMK1bT6X2JbcAaWAhugTRvRBRyuZgd+c22swUeI9g/ntaxF7CY7dYarhZovofCbUNO0G7JesfmNtjYOCw==} '@microsoft/tsdoc@0.16.0': resolution: {integrity: sha512-xgAyonlVVS+q7Vc7qLW0UrJU7rSFcETRWsqdXZtjzRU8dF+6CkozTK4V4y1LwOX7j8r/vHphjDeMeGI4tNGeGA==} @@ -3539,8 +3714,8 @@ packages: '@swc/core': optional: true - '@nestjs/common@11.1.19': - resolution: {integrity: sha512-qeiTt2tv+e5QyDKqG8HlVZb2wx64FEaSGFJouqTSRs+kG44iTfl3xlz1XqVped+rihx4hmjWgL5gkhtdK3E6+Q==} + '@nestjs/common@11.1.21': + resolution: {integrity: sha512-YV1HYDGsm2rnR0vrLKidtrG6jYX5yqiIjeur1j8++dKGqhhsJ6cjMs0RfQRSTUH7IjgDemA59/znQ8nRrE0D9g==} peerDependencies: class-transformer: '>=0.4.1' class-validator: '>=0.13.2' @@ -3552,8 +3727,8 @@ packages: class-validator: optional: true - '@nestjs/core@11.1.19': - resolution: {integrity: sha512-6nJkWa2efrYi+XlU686J9y5L7OvxpLVjT0T/sxRKE7Jvpffiihelup4WSvLvRhdHDjj/5SuoWEwqReXAaaeHmw==} + '@nestjs/core@11.1.21': + resolution: {integrity: sha512-fqo0BHgny3MOuAL8GSfG3ZUKFVVBaBQD/0iyibnwTONT5vPexjQxJzu+945iloVvBDmrnAaRWxC1gqCDEs/AXQ==} engines: {node: '>= 20'} peerDependencies: '@nestjs/common': ^11.0.0 @@ -3583,14 +3758,14 @@ packages: class-validator: optional: true - '@nestjs/platform-express@11.1.19': - resolution: {integrity: sha512-Vpdv8jyCQdThfoTx+UTn+DRYr6H6X02YUqcpZ3qP6G3ZUwtVp7eS+hoQPGd4UuCnlnFG8Wqr2J9bGEzQdi1rIg==} + '@nestjs/platform-express@11.1.21': + resolution: {integrity: sha512-lA3ViycOnz4Df3EstIKpuAVFhqxQixTnjAVk0M+LRyNBlGM6VSCaNJaAIrb9Pcry39T4hTHpNVbRqGLSvhL8gA==} peerDependencies: '@nestjs/common': ^11.0.0 '@nestjs/core': ^11.0.0 - '@nestjs/platform-socket.io@11.1.19': - resolution: {integrity: sha512-gu1nPIEaP5Qjjg/Cl8wXyvwGpdZGzgbtK4KcH65YRAA+GTKUkIHb4BNpLJ27Ymq/wqLJKNEbCjajfzD0BEjMGA==} + '@nestjs/platform-socket.io@11.1.21': + resolution: {integrity: sha512-Tq5JgaVS+auD3DXuRBy8UMU3mf69HJO8Ep+BuRS9GYMXGd/5sdMHqIQvXlXkGih9tQXdeeG9WoqURe/+IjPKng==} peerDependencies: '@nestjs/common': ^11.0.0 '@nestjs/websockets': ^11.0.0 @@ -3611,8 +3786,8 @@ packages: prettier: optional: true - '@nestjs/swagger@11.4.2': - resolution: {integrity: sha512-aBihEogDMj/bLEcaqhkvyX/ZVWUw/bmnhKzR0zwUoyGJikvZyaq7rOPYl/H7Lxkkr3c90SJxyuv1AX2UT1WKlw==} + '@nestjs/swagger@11.4.3': + resolution: {integrity: sha512-LR4BuOj+iBFzhGRnNP0OHjmrPXliDEjrmniXtLsfLDIELjkuUXYCTGjZMqgDdOY+QSabeF59LndaDzOOe+vMmw==} peerDependencies: '@fastify/static': ^8.0.0 || ^9.0.0 '@nestjs/common': ^11.0.1 @@ -3620,6 +3795,7 @@ packages: class-transformer: '*' class-validator: '*' reflect-metadata: ^0.1.12 || ^0.2.0 + typescript: '*' peerDependenciesMeta: '@fastify/static': optional: true @@ -3628,8 +3804,8 @@ packages: class-validator: optional: true - '@nestjs/testing@11.1.19': - resolution: {integrity: sha512-/UFNWXvPEdu4v4DlC5oWLbGKmD27LehLK06b8oLzs6D6lf4vAQTdST8LRAXBadyMUQnVEQWMuBo3CtAVtlfXtQ==} + '@nestjs/testing@11.1.21': + resolution: {integrity: sha512-RhzaUFxr6/bpXWjKIzr7p2eHKMFMLwPgsxJNFcCf2CkkT3UEjW+KRGb7E2JY+fh+ck3zAdvQJrzATDnSsVlFZw==} peerDependencies: '@nestjs/common': ^11.0.0 '@nestjs/core': ^11.0.0 @@ -3641,8 +3817,8 @@ packages: '@nestjs/platform-express': optional: true - '@nestjs/websockets@11.1.19': - resolution: {integrity: sha512-2qo8jtIwwwgkqAI1BtnJ02EaFLrRkKA39eYXS8IhZCHilhBHCWdjnJ5cLcFq4oF+s+KZ7LcLGD/3stxJy8ijzg==} + '@nestjs/websockets@11.1.21': + resolution: {integrity: sha512-2L+jFf6Nbjv7WacngvSoYGYTalqNuXlOkFl9Q5e92XiPK4gR6c4Zw6zFcb7btTY5zf7pITvOoAJIGwf3+gciRA==} peerDependencies: '@nestjs/common': ^11.0.0 '@nestjs/core': ^11.0.0 @@ -3653,6 +3829,10 @@ packages: '@nestjs/platform-socket.io': optional: true + '@noble/hashes@1.4.0': + resolution: {integrity: sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==} + engines: {node: '>= 16'} + '@noble/hashes@1.8.0': resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} engines: {node: ^14.21.3 || >=16} @@ -3673,14 +3853,6 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - '@npmcli/agent@4.0.0': - resolution: {integrity: sha512-kAQTcEN9E8ERLVg5AsGwLNoFb+oEG6engbqAU2P43gD4JEIkNGMHdVQ096FsOAAYpZPB0RSt0zgInKIAS1l5QA==} - engines: {node: ^20.17.0 || >=22.9.0} - - '@npmcli/fs@5.0.0': - resolution: {integrity: sha512-7OsC1gNORBEawOa5+j2pXN9vsicaIOH5cPXxoR6fJOmH6/EXpJB2CajXOu1fPRFun2m1lktEFX11+P89hqO/og==} - engines: {node: ^20.17.0 || >=22.9.0} - '@nuxt/opencollective@0.4.1': resolution: {integrity: sha512-GXD3wy50qYbxCJ652bDrDzgMr3NFEkIS374+IgFQKkCvk9yiYcLvX2XDYr7UyQxf4wK0e+yqDYRubZ0DtOxnmQ==} engines: {node: ^14.18.0 || >=16.10.0, npm: '>=5.10.0'} @@ -3689,164 +3861,160 @@ packages: '@oazapfts/runtime@1.2.0': resolution: {integrity: sha512-fi7dp7dNayyh/vzqhf0ZdoPfC7tJvYfjaE8MBL1yR+iIsH7cFoqHt+DV70VU49OMCqLc7wQa+yVJcSmIRnV4wA==} - '@opentelemetry/api-logs@0.215.0': - resolution: {integrity: sha512-xrFlqhdhUyO8wSRn6DjE0145/HPWSJ5Nm0C7vWua6TdL/FSEAZvEyvdsa9CRXuxo9ebb7j/NEPhEcO62IJ0qUA==} - engines: {node: '>=8.0.0'} - - '@opentelemetry/api@1.9.0': - resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} + '@opentelemetry/api-logs@0.218.0': + resolution: {integrity: sha512-fmEWp5kXlGEc3i/lR698Hz41DfGyN4Tbe4g7L1AxSc7fF8Xeh/FQ9Quqpa9dVA413Q1Ad43QOLzU4JoXgbFPWw==} engines: {node: '>=8.0.0'} '@opentelemetry/api@1.9.1': resolution: {integrity: sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==} engines: {node: '>=8.0.0'} - '@opentelemetry/configuration@0.215.0': - resolution: {integrity: sha512-FSWvDryxjinHROfzEVbJGBw10FqGzLEm2C1LPX6Lot6hvxq3lFJzNLlue8vm64C5yIbqSQVjWsPhYu56ThQS4Q==} + '@opentelemetry/configuration@0.218.0': + resolution: {integrity: sha512-W8wIz7H2R1pufR5jfjb3gU2XkMpm2x/7b1RJcsuzvd70Il/rWWE+g5/Od7hQKrxRTSrTrOWlru101PWXz5I1EQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.9.0 - '@opentelemetry/context-async-hooks@2.7.0': - resolution: {integrity: sha512-MWXggArM+Y11mPS8VOrqxOj+YMGQSRuvhM91eSBX4xFpJa05mpkeVvM8pPux5ElkEjV5RMgrkisrlP/R83SpBQ==} + '@opentelemetry/context-async-hooks@2.7.1': + resolution: {integrity: sha512-OPFBYuXEn1E4ja3Y6eeA7O+ZnLBNcXTV5Cgsn1VaqBZ6hC5FnpZPLBNme1LJY8ZtF4aOujPKFoeWN4ik487KuQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/core@2.7.0': - resolution: {integrity: sha512-DT12SXVwV2eoJrGf4nnsvZojxxeQo+LlNAsoYGRRObPWTeN6APiqZ2+nqDCQDvQX40eLi1AePONS0onoASp3yQ==} + '@opentelemetry/core@2.7.1': + resolution: {integrity: sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/exporter-logs-otlp-grpc@0.215.0': - resolution: {integrity: sha512-MVq+9ma/63XRXc0AcnS+XyWSD6VBYn39OucsvpzjqxTpzTOiGXNxTwsbV3zbnvgUexb5hc2ZjJlZUK2W/19UUw==} + '@opentelemetry/exporter-logs-otlp-grpc@0.218.0': + resolution: {integrity: sha512-hoxrNH1l/Xy6F9WTJ5IK+6j1r9nQFlPOmrnTlhYHTySdunfXLmUCPv3bQtKYntxag9h3wLYBZQ2HI6FOx+BT2g==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-logs-otlp-http@0.215.0': - resolution: {integrity: sha512-U7Qb+TVX2GZH5RSC+Gx9aE5zChKP1kPg87X3PlI/41lWVPJdBIzmgMmuE28MmQlrK84nLHCIqUOOben8YkSzBw==} + '@opentelemetry/exporter-logs-otlp-http@0.218.0': + resolution: {integrity: sha512-Qx+4rpVHzgg89dawcWRHyt+XRXeLnhFz/qBtvggmjkcgPUdr+NAB0/u/eIPA8yAeJV0J80Vz43JZCh/XFvZFGw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-logs-otlp-proto@0.215.0': - resolution: {integrity: sha512-vs2xKKTdt/vKWMuBzw+LZYYCKqulodCRoonWWiyToIQfa6JgbyWjTu/iy6qpBLhLi+t6fNc1bwJGwu3vkot2Jg==} + '@opentelemetry/exporter-logs-otlp-proto@0.218.0': + resolution: {integrity: sha512-1/noQNsp9gXD75HPzgjBrcF1+XTtry7pFAUfxVEJgg7mPv2AawKQuYkhMmJ8qjxz4Ubc3Y8bwvfxevXsKTq4cg==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-metrics-otlp-grpc@0.215.0': - resolution: {integrity: sha512-1TAMliHQvzc+v1OtnLMHSk5sU8BSkJbxIKrWzuCWcQjajWrvem/r5ugLK6agI0PjPz/ADfZju5AVYedlNyeO9g==} + '@opentelemetry/exporter-metrics-otlp-grpc@0.218.0': + resolution: {integrity: sha512-YapQ9vNMX0NSZF6LK5pWAFfjpJleV2O9uYWfYGeb/5F1Kb9rPGK8tZDMJFa/sOksgdFuflDvYuA0B4qjDB4fjQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-metrics-otlp-http@0.215.0': - resolution: {integrity: sha512-FRydO5j7MWnXK9ghfykKxiSM8I5UeiicK/UNl3/mv86xoEKkb+LKz1I3WXgkuYVOQf22VNqbPO58s2W1mVWtEQ==} + '@opentelemetry/exporter-metrics-otlp-http@0.218.0': + resolution: {integrity: sha512-bV7d2OuMpZu2+gAaxUAhzfZ0h3WVZk8ETQUEE3DNSntbTaMpuITjtm8I0rNyHFdm7Ax57K6ty7SgFXlBmOLIvQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-metrics-otlp-proto@0.215.0': - resolution: {integrity: sha512-d8/Sys9MtxLbn0S+RE1pUNcuoI9ZyI4SPfOO+yskSEQiPFoKCTMwwthB8MTY4S8qxCBAWyM+P7QMX+vEIT7PZw==} + '@opentelemetry/exporter-metrics-otlp-proto@0.218.0': + resolution: {integrity: sha512-ubLddKjWULhla9YZRCj/rTBeppjJYE4e9w0icx5mTu3eFhWjQzbV75NYjXuIlEG+NJsBl6d+sTFw5Qu+oej4oQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-prometheus@0.215.0': - resolution: {integrity: sha512-7ghCl1G84jccmxG3B8UwUMZ1OlequBzB1jt5tZ4DDiAyVKeA4Roz5D6VK8SQ0ZyBQffVyX/rtXrpVXKVzRCGfg==} + '@opentelemetry/exporter-prometheus@0.218.0': + resolution: {integrity: sha512-RT5oEyu1kddZJ1vt7/BUo5wV+P7hpNAESsR3dUd3+8deHuX7gWNoCOZn+SfDT+hJHlIJ5h/AxiCLXIrutswDJg==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-trace-otlp-grpc@0.215.0': - resolution: {integrity: sha512-+SuWfPFVjPTvHJhlzTCBetLsPVu86xSFPR3fv8TN+H7lpe5aZzF96TUsfMHDR0lwpIwlJpG57CJnGalIfrpXkg==} + '@opentelemetry/exporter-trace-otlp-grpc@0.218.0': + resolution: {integrity: sha512-3fXxVQEj9TNAFaCi79JeFKfeLd0sDtInaR3gaZDVlzNSPHtz8PZuCV34JKWjD4XXzT20IdMe8IpX6mRVNDA4Tw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-trace-otlp-http@0.215.0': - resolution: {integrity: sha512-k4J9ISeGpb0Bm/wCrlcrbroMFTkiWMrdhNxQGrlktxLy127Yzd4/7nrTawn5d/ApktYTknvdixsE6++34Qfi1w==} + '@opentelemetry/exporter-trace-otlp-http@0.218.0': + resolution: {integrity: sha512-8dqezsmPhtKitIK/eTipZhYl9EX2/gNQ5zUMhaz3uxEURwfkNf8IPvo6yNfrzbxdtpAOybS/+h7wmIWYqFSpiw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-trace-otlp-proto@0.215.0': - resolution: {integrity: sha512-+QclHuJmlp/I3Z2fNn+j1dAajMjJqJ4Sgo8ajwiK6Tzmg5SNwBGmBX66AZvTLe/3/bc3L7bo90m9gsaJBrzEsA==} + '@opentelemetry/exporter-trace-otlp-proto@0.218.0': + resolution: {integrity: sha512-r1Msf8SNLRmwh9J6XQ5uh82D7CdDWMNHnPB7LAVHjzut0TkSeKc5KcIvr4SvHvfk/xwN5gxC+VLKQ1k0o8PSPw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-zipkin@2.7.0': - resolution: {integrity: sha512-tbzcYDmZWtX4hgJn15qP7/iYFVd1yzbUloBuSYsQtn0XQTxJsG7vgwkPKEBellriH0XJmlZJxYtWkHpwzHBhaQ==} + '@opentelemetry/exporter-zipkin@2.7.1': + resolution: {integrity: sha512-mfsD9bKAxcKrh5+y08TPodvClBO0CznBE3p79YAGnO81WI4LrdsGA65T53e4iTSbCalW4WaUpkbeJcbpyIUHfg==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.0.0 - '@opentelemetry/host-metrics@0.36.2': - resolution: {integrity: sha512-eMdea86cfIqx3cdFpcKU3StrjqFkQDIVp7NANVnVWO8O6hDw/DBwGwu4Gi1wJCuoQ2JVwKNWQxUTSRheB6O29Q==} + '@opentelemetry/host-metrics@0.38.3': + resolution: {integrity: sha512-8iSOA8VPGoB5p/RIC8n/dcSe4cluCEWoznWENZfXR8sWQOQvergFu7v798xp7S5WQlZo1zfn1nVXx8dbyQ9m6Q==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-http@0.215.0': - resolution: {integrity: sha512-ip9iNoRRVxDyP8LVfdqqI6OwbOwzxTl4SaP1WDKJq0sDsgpOr7rIOFj7gV8yKl4F5PdDOUYy8VqdgIOWZRlGBw==} + '@opentelemetry/instrumentation-http@0.218.0': + resolution: {integrity: sha512-x9djaqdzpT8WAboep1H9nCAQ1E+MMsm08TNfA02TqM3bNNddZeiim+E3KMWVQFaX6JpUy7V0nm/wfN/K2Em+Zw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-ioredis@0.63.0': - resolution: {integrity: sha512-x+h/uq7mstqr7TwU1q0MdmMkyU1SDZcmd/ErXbdNfScmXMcYfo8sCRzMsL9UwukSdaU3ccYYpYweGXghv9xN0Q==} + '@opentelemetry/instrumentation-ioredis@0.66.0': + resolution: {integrity: sha512-UfTAcaBKCzLUZ9opvfOLV4bH46XiNFqUsKykfPCIefDIxJ1iUYtMOucNaiZ+/kjQdPy5i6Ef5tk2IAjxol4X1w==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-nestjs-core@0.61.0': - resolution: {integrity: sha512-e/zpwFbEyQFK8uINyFqbeQsA6PW5+hKI+eJj8L98lz1FnQSbRsNMz3Z8c0KYWcDqbg857DpB97s9P3lXdtwccg==} + '@opentelemetry/instrumentation-nestjs-core@0.64.0': + resolution: {integrity: sha512-PW1ArxryMwF8/IXq1nzlQs7tmr/fWd1tf71AHevZT3Fm0hW7jRX9JEfYgIAcKDvmbqcJEr5K1224NEimrRPbuQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-pg@0.67.0': - resolution: {integrity: sha512-1b1o/9nelDwoE3+EucZ9eHZsdUgji799C94lX1ZPy6O0EVjdTj3HczLL6z3GqPGZHmV4OpmJjGz8kuLtuPjCGA==} + '@opentelemetry/instrumentation-pg@0.70.0': + resolution: {integrity: sha512-g8WXwwOUXfjiEmATwjB/33QKE2AkIpNe4KIuJJh4djtXgCL0Wne+AzAfjuDIAspGvO1txQp8ibKsLd3SBmcvJA==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation@0.215.0': - resolution: {integrity: sha512-SyJONuqypQ2xWdYMy99vF7JhZ2kDTGx4oRmM/jZV+kRtZ96JTnJmEINbIJgHz7Gnhtw0bimHwbPy/pguA5wpPQ==} + '@opentelemetry/instrumentation@0.218.0': + resolution: {integrity: sha512-mIZil8Es+sYDK5m+DQiwAwF57F14TF2YlEqvIjZ/RQWcxDBwRGsKfdK2Tv65OU9meQKCMzSIFS9mxAcnAb6Bkg==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/otlp-exporter-base@0.215.0': - resolution: {integrity: sha512-lHrfbmeLSmesGSkkHiqDwOzfaEMSWXdc7q6UoLfbW8byONCb+bE/zkAr0kapN4US1baT/2nbpNT7Cn9XoB96Vg==} + '@opentelemetry/otlp-exporter-base@0.218.0': + resolution: {integrity: sha512-ZwqpkNL5W7RyGJPDZ9g06DvKp8KFTWPJPN12anpMQYSKpTSU0z3EIZuPq9vPGpS8siFyOqDYDAuCwlNO9FqgbA==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/otlp-grpc-exporter-base@0.215.0': - resolution: {integrity: sha512-WkuHkUrhwNxTKrm7Xuf6S+HmLNbk2T8S2YiZhN606RfgetSQb9xLp4NizWLwXvw63uxGsBaK262dirFO2yht2g==} + '@opentelemetry/otlp-grpc-exporter-base@0.218.0': + resolution: {integrity: sha512-H/lCGJ536N98VpYJOaWTQOkv4Dx6TnmStK6Rqfu1W7KkFbPAx04hjdYEMZF/YbnHzPUSIK4kM6OE2GKGBTpV9A==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/otlp-transformer@0.215.0': - resolution: {integrity: sha512-cWwBvaV+vkXHkSoTYR8hGw+AW03UlgTr6xtrUKOMeum3T+8vffYXIfXu6KY5MLu8O9QtoBKqaKWw9I5xoOepng==} + '@opentelemetry/otlp-transformer@0.218.0': + resolution: {integrity: sha512-CFaKH87WAzjuJ4awowTTLzUvMfaRfiOFG5+qm5S5ncyalRtN4ecQ+YmuANJSCrVPuvZFEkUgKhBPBndxi3rHsQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/propagator-b3@2.7.0': - resolution: {integrity: sha512-HNm+tdXY5i8dzAo4YankchNWdZ4Z1Boop7lhbb3wltWT0MwEMo0QADRJwrF83pXEeDT+5Bmq4J8sStFaUywE3g==} + '@opentelemetry/propagator-b3@2.7.1': + resolution: {integrity: sha512-RJid6E2CKyeGfKBzXKF21ejabGMHypFkPAh3qZ+NvI+SGjuIye79t3PmiqcDgtRzdKH6ynXzbfslQ8DfpRUg2A==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/propagator-jaeger@2.7.0': - resolution: {integrity: sha512-lKMAjekRkFYWrjmPTaxUJt+V8Mr1iB94sP3HDZZCmdZ/LUV/wtqAGqXhgnkIbdlnWxxvEs9MGEIMdJC+xObMFg==} + '@opentelemetry/propagator-jaeger@2.7.1': + resolution: {integrity: sha512-KMjVBHzP4N60bOzxja76M1F1hZZ43lGPga5ix+mkv9+kk1nx9SbkxSvJsMbuVUxdPQmsPTqGShmhN8ulrMOg6Q==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' @@ -3855,44 +4023,44 @@ packages: resolution: {integrity: sha512-VCghU1JYs/4gP6Gqf/xro9MEsZ7LrMv2uONVsaESKL38ZOB9BqnI98FfS23wjMnHlpuE+TTaWSoAVNpTwYXzjw==} engines: {node: ^18.19.0 || >=20.6.0} - '@opentelemetry/resources@2.7.0': - resolution: {integrity: sha512-K+oi0hNMv94EpZbnW3eyu2X6SGVpD3O5DhG2NIp65Hc7lhAj9brRXTAVzh3wB82+q3ThakEf7Zd7RsFUqcTc7A==} + '@opentelemetry/resources@2.7.1': + resolution: {integrity: sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.3.0 <1.10.0' - '@opentelemetry/sdk-logs@0.215.0': - resolution: {integrity: sha512-y3ucOmphzc4vgBTyIGchs+N/1rkACmoka8QalT2z1LBNM232Z17zMYayHcMl+dgMoOadZ0b72UZv7mDtqy1cFA==} + '@opentelemetry/sdk-logs@0.218.0': + resolution: {integrity: sha512-QvnNdugatFTVCJXH0Mcu7GOOJSylA9j127kIezOE4YwTI4YbowRons2K4WZTv5FMS8T4q9P0NdaRHdkSmeAIag==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.4.0 <1.10.0' - '@opentelemetry/sdk-metrics@2.7.0': - resolution: {integrity: sha512-Vd7h95av/LYRsAVN7wbprvvJnHkq7swMXAo7Uad0Uxf9jl6NSReLa0JNivrcc5BVIx/vl2t+cgdVQQbnVhsR9w==} + '@opentelemetry/sdk-metrics@2.7.1': + resolution: {integrity: sha512-MpDJdkiFDs3Pm1RHO3KByuZbuBdJEXEAkiC0+yJdsZGVCdf1RpHR6n+LHDcS7ffmfrt5kVCzJSCfm4z2C7v0uQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.9.0 <1.10.0' - '@opentelemetry/sdk-node@0.215.0': - resolution: {integrity: sha512-YunKvZOMhYNMBJ66YRjbGShuoV/w1y21U7MGPRx0iPJenPszOddtYEQFJv8piAEOn94BUFIfJHtHjptrHsGiIA==} + '@opentelemetry/sdk-node@0.218.0': + resolution: {integrity: sha512-tPMjHrLV5gsfNdYqoRHjeGbCAZBXXD9c1Qo/2ut7VwnUABDNh76xNxrT0SEhkIIJuCN45bbN1vZnYL1gY0IkOg==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.3.0 <1.10.0' - '@opentelemetry/sdk-trace-base@2.7.0': - resolution: {integrity: sha512-Yg9zEXJB50DLVLpsKPk7NmNqlPlS+OvqhJGh0A8oawIOTPOwlm4eXs9BMJV7L79lvEwI+dWtAj+YjTyddV336A==} + '@opentelemetry/sdk-trace-base@2.7.1': + resolution: {integrity: sha512-NAYIlsF8MPUsKqJMiDQJTMPOmlbawC1Iz/omMLygZ1C9am8fTKYjTaI+OZM+WTY3t3Glo0wnOg/6/pac6RGPPw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.3.0 <1.10.0' - '@opentelemetry/sdk-trace-node@2.7.0': - resolution: {integrity: sha512-RrFHOXw0IYp/OThew6QORdybnnLitUAUMCJKcQNBYS0hDkCYarO2vTkVxfrGxCIqd5XHSMvbCpBd/T8ZMw8oSg==} + '@opentelemetry/sdk-trace-node@2.7.1': + resolution: {integrity: sha512-pCpQxU68lV+I9s9svqMyVu5iHdDDUnqUpSxqwyCU8A9ejEsSnMPCbearwsUO4yk08ZJzAIUCFuReMdVQvHrdvg==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/semantic-conventions@1.40.0': - resolution: {integrity: sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw==} + '@opentelemetry/semantic-conventions@1.41.1': + resolution: {integrity: sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA==} engines: {node: '>=14'} '@opentelemetry/sql-common@0.41.2': @@ -3901,8 +4069,8 @@ packages: peerDependencies: '@opentelemetry/api': ^1.1.0 - '@oxc-project/types@0.127.0': - resolution: {integrity: sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==} + '@oxc-project/types@0.130.0': + resolution: {integrity: sha512-ibD2usx9JRu7f5pu2tMKMI4cpA4NgXJQoYRP4pQ7Pxmn1l6k/53qWtQWZayhYy3X4QZkt90Ot+mJEaeXouio6Q==} '@paralleldrive/cuid2@2.3.1': resolution: {integrity: sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==} @@ -3995,6 +4163,43 @@ packages: resolution: {integrity: sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==} engines: {node: '>= 10.0.0'} + '@peculiar/asn1-cms@2.7.0': + resolution: {integrity: sha512-hew63shtzzvBcSHbhm+cyAmKe6AIfinT9hzEqSPjDC6opTTMKmTkQ0gHuN2KsWlvqiKw1S/fS94fhag/FJkioQ==} + + '@peculiar/asn1-csr@2.7.0': + resolution: {integrity: sha512-VVsAyGqErT9D1SY4aEqozThXMVI+ssVRiv2DDeYuvpBKLIgZ3hYs3Ay3u/VSoKq6ESFi9cf6rf3IOOzfwh7oMA==} + + '@peculiar/asn1-ecc@2.7.0': + resolution: {integrity: sha512-n7KEs/Q/wrB415cxy4fHOBhegp4NdJ15fkJPwcB/3/8iNBQC2L/N7SChJPKDJPZGYH0jD4Tg4/0vnHmwghnbKw==} + + '@peculiar/asn1-pfx@2.7.0': + resolution: {integrity: sha512-V/nrlQVmhg7lYAsM7E13UDL5erAwFv6kCIVFqNaMIHSVi7dngcT839JkRTkQBqznMG98l2XjxYk74ZztAohZzA==} + + '@peculiar/asn1-pkcs8@2.7.0': + resolution: {integrity: sha512-9GTl1nE8Mx1kTZ+7QyYatDyKsm34QcWRBFkY1iPvWC3X4Dona5s/tlLiQsx5WzVdZqiMBZNYT0buyw4/vbhnjw==} + + '@peculiar/asn1-pkcs9@2.7.0': + resolution: {integrity: sha512-Bh7m+OuIaSEllPQcSd9OSp93F4ROWH7sbITWV8MI+8dwsjE5111/87VxiWVvYFKyww3vp39geLv9ENqhwWHcew==} + + '@peculiar/asn1-rsa@2.7.0': + resolution: {integrity: sha512-/qvENQrXyTZURjMqSeofHul0JJt2sNSzSwk36pl2olkHbaioMQgrASDZAlHXl0xUlnVbHj0uGgOrBMTb5x2aJQ==} + + '@peculiar/asn1-schema@2.7.0': + resolution: {integrity: sha512-W8ZfWzLmQnrcky+eh3tni4IozMdqBDiHWU0N+vve/UGjMaUs8c0L7A2oEdkBXS8rTpWDpK/aoI3DG/L/hxmxPg==} + + '@peculiar/asn1-x509-attr@2.7.0': + resolution: {integrity: sha512-NS8e7SOgXipkzUPLF/sce7ukpMpWjhxYsH0n6Y+bHYo4TTxOb95Zv7hqwSuL212mj5YxovjdOKQOgH1As3E94w==} + + '@peculiar/asn1-x509@2.7.0': + resolution: {integrity: sha512-mUn9RRrkGDnG4ALfunDmzyRW5dg+sWCj/pfnCCqEHYbkGxEpvUt6iVJv8Yw1cyp6SWZ26ZE5oSmI5SqEaen15g==} + + '@peculiar/utils@2.0.3': + resolution: {integrity: sha512-+oL3HPFRIZ1St2K50lWCXiioIgSoxzz7R1J3uF6neO2yl1sgmpgY6XXJH4BdpoDkMWznQTeYF6oWNDZLCdQ4eQ==} + + '@peculiar/x509@1.14.3': + resolution: {integrity: sha512-C2Xj8FZ0uHWeCXXqX5B4/gVFQmtSkiuOolzAgutjTfseNOHT3pUjljDZsTSxXFGgio54bCzVFqmEOUrIVk8RDA==} + engines: {node: '>=20.0.0'} + '@photo-sphere-viewer/core@5.14.1': resolution: {integrity: sha512-qrwUudrX9YZms4c2shlY/H3jUP0oh9FyGEqIDr/95ulNZgKbhQ6C/i8zDQ4j8ooFR4+z5FDORQtGvLgPyX8VCA==} @@ -4036,8 +4241,8 @@ packages: resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} - '@playwright/test@1.59.1': - resolution: {integrity: sha512-PG6q63nQg5c9rIi4/Z5lR5IVF7yU5MqmKaPOe0HSc0O2cX1fPi96sUQu5j7eo4gKCkB2AnNGoWt7y4/Xx3Kcqg==} + '@playwright/test@1.60.0': + resolution: {integrity: sha512-O71yZIbAh/PxDMNGns37GHBIfrVkEVyn+AXyIa5dOTfb4/xNvRWV+Vv/NMbNCtODB/pO7vLlF2OTmMVLhmr7Ag==} engines: {node: '>=18'} hasBin: true @@ -4049,8 +4254,8 @@ packages: resolution: {integrity: sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==} engines: {node: '>=12.22.0'} - '@pnpm/npm-conf@2.3.1': - resolution: {integrity: sha512-c83qWb22rNRuB0UaVCI0uRPNRr8Z0FWnEIvT47jiHAmOIUHbBOg5XvV7pM5x+rKn9HRpjxquDbXYSXr3fAKFcw==} + '@pnpm/npm-conf@3.0.2': + resolution: {integrity: sha512-h104Kh26rR8tm+a3Qkc5S4VLYint3FE48as7+/5oCEcKR2idC/pF1G6AhIXKI+eHPJa/3J9i5z0Al47IeGHPkA==} engines: {node: '>=12'} '@polka/url@1.0.0-next.29': @@ -4062,20 +4267,20 @@ packages: '@protobufjs/base64@1.1.2': resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} - '@protobufjs/codegen@2.0.4': - resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==} + '@protobufjs/codegen@2.0.5': + resolution: {integrity: sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==} '@protobufjs/eventemitter@1.1.0': resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==} - '@protobufjs/fetch@1.1.0': - resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==} + '@protobufjs/fetch@1.1.1': + resolution: {integrity: sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==} '@protobufjs/float@1.0.2': resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} - '@protobufjs/inquire@1.1.0': - resolution: {integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==} + '@protobufjs/inquire@1.1.2': + resolution: {integrity: sha512-pa0vFRuws4wkvaXKK1uXZMAwAX4/t8ANaJo45iw/oQHNQ9q5xUzwgFmVJGXiga2BeN+zpX7Vf9vmsiIa2J+MUw==} '@protobufjs/path@1.1.2': resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} @@ -4083,8 +4288,8 @@ packages: '@protobufjs/pool@1.1.0': resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} - '@protobufjs/utf8@1.1.0': - resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} + '@protobufjs/utf8@1.1.1': + resolution: {integrity: sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==} '@react-email/body@0.3.0': resolution: {integrity: sha512-uGo0BOOzjbMUo3lu+BIDWayvn5o6Xyfmnlla5VGf05n8gHMvO1ll7U4FtzWe3hxMLwt53pmc4iE0M+B5slG+Ug==} @@ -4205,8 +4410,8 @@ packages: react: ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^18.0 || ^19.0 || ^19.0.0-rc - '@react-email/render@2.0.7': - resolution: {integrity: sha512-XCsqujKURb4egU8+z7RX1/yxRx1Qo89uGhy6UXyB5Oxq1SK+48t0AD/3qeuDGgDvyS+Ti+0oDT3nn5/dcG4Ttg==} + '@react-email/render@2.0.8': + resolution: {integrity: sha512-5udvVr3U/WuGJZfLdLBOhkzrqRWd2Q5ZYmF7ppcy7FzWcwgshdqLMNqJOXcVzAXJXg/2bm7D+WGJzTtZOZMQnQ==} engines: {node: '>=20.0.0'} peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc @@ -4279,103 +4484,103 @@ packages: '@codemirror/state': ^6.0.0 '@codemirror/view': ^6.0.0 - '@rolldown/binding-android-arm64@1.0.0-rc.17': - resolution: {integrity: sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ==} + '@rolldown/binding-android-arm64@1.0.1': + resolution: {integrity: sha512-fJI3I0r3C3Oj/zdBCpaCmBRZYf07xpaq4yCfDDoSFm+beWNzbIl26puW8RraUdugoJw/95zerNOn6jasAhzSmg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rolldown/binding-darwin-arm64@1.0.0-rc.17': - resolution: {integrity: sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw==} + '@rolldown/binding-darwin-arm64@1.0.1': + resolution: {integrity: sha512-cKnAhWEsV7TPcA/5EAteDp6KcJZBQ2G+BqE7zayMMi7kMvwRsbv7WT9aOnn0WNl4SKEIf43vjS31iUPu80nzXg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-x64@1.0.0-rc.17': - resolution: {integrity: sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw==} + '@rolldown/binding-darwin-x64@1.0.1': + resolution: {integrity: sha512-YKrVwQjIRBPo+5G/u03wGjbdy4q7pyzCe93DK9VJ7zkVmeg8LJ7GbgsiHWdR4xSoe4CAXRD7Bcjgbtr64bkXNg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@rolldown/binding-freebsd-x64@1.0.0-rc.17': - resolution: {integrity: sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw==} + '@rolldown/binding-freebsd-x64@1.0.1': + resolution: {integrity: sha512-z/oBsREo46SsFqBwYtFe0kpJeBijAT48O/WXLI4suiCLBkr03RTtTJMCzSdDd2znlh8VJizL09XVkQgk8IZonw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.17': - resolution: {integrity: sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ==} + '@rolldown/binding-linux-arm-gnueabihf@1.0.1': + resolution: {integrity: sha512-ik8q7GM11zxvYxFc2PeDcT6TBvhCQMaUxfph/M5l9sKuTs/Sjg3L+Byw0F7w0ZVLBZmx30P+gG0ECzzN+MFcmQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.17': - resolution: {integrity: sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q==} + '@rolldown/binding-linux-arm64-gnu@1.0.1': + resolution: {integrity: sha512-QoSx2EkyrrdZ6kcyE8stqZ62t0Yra8Fs5ia9lOxJrh6TMQJK7gQKmscdTHf7pOXKREKrVwOtJcQG3qVSfc866A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-arm64-musl@1.0.0-rc.17': - resolution: {integrity: sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==} + '@rolldown/binding-linux-arm64-musl@1.0.1': + resolution: {integrity: sha512-uwNwFpwKeNiZawfAWBgg0VIztPTV3ihhh1vV334h9ivnNLorxnQMU6Fz8wG1Zb4Qh9LC1/MkcyT3YlDXG3Rsgg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.17': - resolution: {integrity: sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==} + '@rolldown/binding-linux-ppc64-gnu@1.0.1': + resolution: {integrity: sha512-zY1bul7OWr7DFBiJ++wofXvnr8B45ce3QsQUhKrIhXsygAh7bTkwyeM1bi1a2g5C/yC/N8TZyGDEoMfm/l9mpg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.17': - resolution: {integrity: sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==} + '@rolldown/binding-linux-s390x-gnu@1.0.1': + resolution: {integrity: sha512-0frlsT/f4Ft6I7SMESTKnF3cZsdicQn1dCMkF/jT9wDLE+gGoiQfv1nmT9e+s7s/fekvvy6tZM2jHvI2tkbJDQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-gnu@1.0.0-rc.17': - resolution: {integrity: sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA==} + '@rolldown/binding-linux-x64-gnu@1.0.1': + resolution: {integrity: sha512-XABVmGp9Tg0WspTVvwduTc4fpqy6JnAUrSQe6OuyqD/03nI7r0O9OWUkMIwFrjKAIqolvqoA4ZrJppgwE0Gxmw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-musl@1.0.0-rc.17': - resolution: {integrity: sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw==} + '@rolldown/binding-linux-x64-musl@1.0.1': + resolution: {integrity: sha512-bV4fzswuzVcKD90o/VM6QqKxnxlDq0g2BISDLNVmxrnhpv1DDbyPhCIjYfvzYLV+MvkKKnQt2Q6AO86SEBULUQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@rolldown/binding-openharmony-arm64@1.0.0-rc.17': - resolution: {integrity: sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==} + '@rolldown/binding-openharmony-arm64@1.0.1': + resolution: {integrity: sha512-/Mh0Zhq3OP7fVs0kcQHZP6lZEthMGTaSf8UBQYSFEZDWGXXlEC+nJ6EqenaK2t4LBXMe3A+K/G2BVXXdtOr4PQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@rolldown/binding-wasm32-wasi@1.0.0-rc.17': - resolution: {integrity: sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA==} + '@rolldown/binding-wasm32-wasi@1.0.1': + resolution: {integrity: sha512-+1xc9X45l8ufsBAm6Gjvx2qDRIY9lTVt0cgWNcJ+1gdhXvkbxePA60yRTwSTuXL09CMhyJmjpV7E3NoyxbqFQQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [wasm32] - '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.17': - resolution: {integrity: sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA==} + '@rolldown/binding-win32-arm64-msvc@1.0.1': + resolution: {integrity: sha512-1D+UqZdfnuR+Jy1GgMJwi85bD40H21uNmOPRWQhw4oRSuolZ/B5rixZ45DK2KXOTCvmVCecauWgEhbw8bI7tOw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.0.0-rc.17': - resolution: {integrity: sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg==} + '@rolldown/binding-win32-x64-msvc@1.0.1': + resolution: {integrity: sha512-INAycaWuhlOK3wk4mRHGsdgwYWmd9cChdPdE9bwWmy6rn9VqVNYNFGhOdXrofXUxwHIncSiPNb8tNm8knDVIeQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] - '@rolldown/pluginutils@1.0.0-rc.17': - resolution: {integrity: sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==} + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} '@rollup/pluginutils@5.3.0': resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} @@ -4386,141 +4591,141 @@ packages: rollup: optional: true - '@rollup/rollup-android-arm-eabi@4.55.1': - resolution: {integrity: sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg==} + '@rollup/rollup-android-arm-eabi@4.60.4': + resolution: {integrity: sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.55.1': - resolution: {integrity: sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg==} + '@rollup/rollup-android-arm64@4.60.4': + resolution: {integrity: sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.55.1': - resolution: {integrity: sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==} + '@rollup/rollup-darwin-arm64@4.60.4': + resolution: {integrity: sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.55.1': - resolution: {integrity: sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ==} + '@rollup/rollup-darwin-x64@4.60.4': + resolution: {integrity: sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.55.1': - resolution: {integrity: sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg==} + '@rollup/rollup-freebsd-arm64@4.60.4': + resolution: {integrity: sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.55.1': - resolution: {integrity: sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw==} + '@rollup/rollup-freebsd-x64@4.60.4': + resolution: {integrity: sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.55.1': - resolution: {integrity: sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==} + '@rollup/rollup-linux-arm-gnueabihf@4.60.4': + resolution: {integrity: sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA==} cpu: [arm] os: [linux] libc: [glibc] - '@rollup/rollup-linux-arm-musleabihf@4.55.1': - resolution: {integrity: sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==} + '@rollup/rollup-linux-arm-musleabihf@4.60.4': + resolution: {integrity: sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w==} cpu: [arm] os: [linux] libc: [musl] - '@rollup/rollup-linux-arm64-gnu@4.55.1': - resolution: {integrity: sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==} + '@rollup/rollup-linux-arm64-gnu@4.60.4': + resolution: {integrity: sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg==} cpu: [arm64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-arm64-musl@4.55.1': - resolution: {integrity: sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==} + '@rollup/rollup-linux-arm64-musl@4.60.4': + resolution: {integrity: sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A==} cpu: [arm64] os: [linux] libc: [musl] - '@rollup/rollup-linux-loong64-gnu@4.55.1': - resolution: {integrity: sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==} + '@rollup/rollup-linux-loong64-gnu@4.60.4': + resolution: {integrity: sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ==} cpu: [loong64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-loong64-musl@4.55.1': - resolution: {integrity: sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==} + '@rollup/rollup-linux-loong64-musl@4.60.4': + resolution: {integrity: sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw==} cpu: [loong64] os: [linux] libc: [musl] - '@rollup/rollup-linux-ppc64-gnu@4.55.1': - resolution: {integrity: sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==} + '@rollup/rollup-linux-ppc64-gnu@4.60.4': + resolution: {integrity: sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg==} cpu: [ppc64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-ppc64-musl@4.55.1': - resolution: {integrity: sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==} + '@rollup/rollup-linux-ppc64-musl@4.60.4': + resolution: {integrity: sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A==} cpu: [ppc64] os: [linux] libc: [musl] - '@rollup/rollup-linux-riscv64-gnu@4.55.1': - resolution: {integrity: sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==} + '@rollup/rollup-linux-riscv64-gnu@4.60.4': + resolution: {integrity: sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA==} cpu: [riscv64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-riscv64-musl@4.55.1': - resolution: {integrity: sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==} + '@rollup/rollup-linux-riscv64-musl@4.60.4': + resolution: {integrity: sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw==} cpu: [riscv64] os: [linux] libc: [musl] - '@rollup/rollup-linux-s390x-gnu@4.55.1': - resolution: {integrity: sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==} + '@rollup/rollup-linux-s390x-gnu@4.60.4': + resolution: {integrity: sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ==} cpu: [s390x] os: [linux] libc: [glibc] - '@rollup/rollup-linux-x64-gnu@4.55.1': - resolution: {integrity: sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==} + '@rollup/rollup-linux-x64-gnu@4.60.4': + resolution: {integrity: sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ==} cpu: [x64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-x64-musl@4.55.1': - resolution: {integrity: sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==} + '@rollup/rollup-linux-x64-musl@4.60.4': + resolution: {integrity: sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg==} cpu: [x64] os: [linux] libc: [musl] - '@rollup/rollup-openbsd-x64@4.55.1': - resolution: {integrity: sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==} + '@rollup/rollup-openbsd-x64@4.60.4': + resolution: {integrity: sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA==} cpu: [x64] os: [openbsd] - '@rollup/rollup-openharmony-arm64@4.55.1': - resolution: {integrity: sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw==} + '@rollup/rollup-openharmony-arm64@4.60.4': + resolution: {integrity: sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg==} cpu: [arm64] os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.55.1': - resolution: {integrity: sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g==} + '@rollup/rollup-win32-arm64-msvc@4.60.4': + resolution: {integrity: sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.55.1': - resolution: {integrity: sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA==} + '@rollup/rollup-win32-ia32-msvc@4.60.4': + resolution: {integrity: sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-gnu@4.55.1': - resolution: {integrity: sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg==} + '@rollup/rollup-win32-x64-gnu@4.60.4': + resolution: {integrity: sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw==} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.55.1': - resolution: {integrity: sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw==} + '@rollup/rollup-win32-x64-msvc@4.60.4': + resolution: {integrity: sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw==} cpu: [x64] os: [win32] @@ -4539,8 +4744,8 @@ packages: '@sideway/pinpoint@2.0.0': resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==} - '@sinclair/typebox@0.27.8': - resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + '@sinclair/typebox@0.27.10': + resolution: {integrity: sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==} '@sindresorhus/is@4.6.0': resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} @@ -4574,8 +4779,8 @@ packages: '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} - '@sveltejs/acorn-typescript@1.0.9': - resolution: {integrity: sha512-lVJX6qEgs/4DOcRTpo56tmKzVPtoWAaVbL4hfO7t7NVwl9AAXzQR6cihesW1BmNMPl+bK6dreu2sOKBP2Q9CIA==} + '@sveltejs/acorn-typescript@1.0.10': + resolution: {integrity: sha512-4WfKk68eTih+MiJD4fSbxN7E8kVBmTMPWHUPYjvl2N0rMs53YLTT8/YjKU5Dtnz5LqDjl7LEw4U7lXR2W3J5WA==} peerDependencies: acorn: ^8.9.0 @@ -4591,8 +4796,8 @@ packages: svelte: ^5.0.0 vite: ^6.3.0 || >=7.0.0 - '@sveltejs/kit@2.57.1': - resolution: {integrity: sha512-VRdSbB96cI1EnRh09CqmnQqP/YJvET5buj8S6k7CxaJqBJD4bw4fRKDjcarAj/eX9k2eHifQfDH8NtOh+ZxxPw==} + '@sveltejs/kit@2.60.1': + resolution: {integrity: sha512-mQjlkNo+rJvpln7V2IGY2j99BqhcFbS4UN0AQNKNYfhBAFZTuCDAdW3a1sgf330mvtNvsBXn3HpAhcmvdJTcIQ==} engines: {node: '>=18.13'} hasBin: true peerDependencies: @@ -4607,8 +4812,8 @@ packages: typescript: optional: true - '@sveltejs/vite-plugin-svelte@7.0.0': - resolution: {integrity: sha512-ILXmxC7HAsnkK2eslgPetrqqW1BKSL7LktsFgqzNj83MaivMGZzluWq32m25j2mDOjmSKX7GGWahePhuEs7P/g==} + '@sveltejs/vite-plugin-svelte@7.1.2': + resolution: {integrity: sha512-DrUBA2UXRfDmUX/ZTiEopd3X40yavsJF1FX2RygcuIScHL7o5YX1fMvoYnDhjeJQC4weCOklirpNWlcb2NiSeA==} engines: {node: ^20.19 || ^22.12 || >=24} peerDependencies: svelte: ^5.46.4 @@ -4692,86 +4897,86 @@ packages: resolution: {integrity: sha512-LnhVjMWyMQV9ZmeEy26maJk+8HTIbd59cH4F2MJ439k9DqejRisfFNGAPvRYlKETuh9LrImlS8aKsBgKjMA8WA==} engines: {node: '>=14'} - '@swc/core-darwin-arm64@1.15.30': - resolution: {integrity: sha512-VvpP+vq08HmGYewMWvrdsxh9s2lthz/808zXm8Yu5kaqeR8Yia2b0eYXleHQ3VAjoStUDk6LzTheBW9KXYQdMA==} + '@swc/core-darwin-arm64@1.15.33': + resolution: {integrity: sha512-N+L0uXhuO7FIfzqwgxmzv0zIpV0qEp8wPX3QQs2p4atjMoywup2JTeDlXPw+z9pWJGCae3JjM+tZ6myclI+2gA==} engines: {node: '>=10'} cpu: [arm64] os: [darwin] - '@swc/core-darwin-x64@1.15.30': - resolution: {integrity: sha512-WiJA0hiZI3nwQAO6mu5RqigtWGDtth4Hiq6rbZxAaQyhIcqKIg5IoMRc1Y071lrNJn29eEDMC86Rq58xgUxlDg==} + '@swc/core-darwin-x64@1.15.33': + resolution: {integrity: sha512-/Il4QHSOhV4FekbsDtkrNmKbsX26oSysvgrRswa/RYOHXAkwXDbB4jaeKq6PsJLSPkzJ2KzQ061gtBnk0vNHfA==} engines: {node: '>=10'} cpu: [x64] os: [darwin] - '@swc/core-linux-arm-gnueabihf@1.15.30': - resolution: {integrity: sha512-YANuFUo48kIT6plJgCD0keae9HFXfjxsbvsgevqc0hr/07X/p7sAWTFOGYEc2SXcASaK7UvuQqzlbW8pr7R79g==} + '@swc/core-linux-arm-gnueabihf@1.15.33': + resolution: {integrity: sha512-C64hBnBxq4viOPQ8hlx+2lJ23bzZBGnjw7ryALmS+0Q3zHmwO8lw1/DArLENw4Q18/0w5wdEO1k3m1wWNtKGqQ==} engines: {node: '>=10'} cpu: [arm] os: [linux] - '@swc/core-linux-arm64-gnu@1.15.30': - resolution: {integrity: sha512-VndG8jaR4ugY6u+iVOT0Q+d2fZd7sLgjPgN8W/Le+3EbZKl+cRfFxV7Eoz4gfLqhmneZPdcIzf9T3LkgkmqNLg==} + '@swc/core-linux-arm64-gnu@1.15.33': + resolution: {integrity: sha512-TRJfnJbX3jqpxRDRoieMzRiCBS5jOmXNb3iQXmcgjFEHKLnAgK1RZRU8Cq1MsPqO4jAJp/ld1G4O3fXuxv85uw==} engines: {node: '>=10'} cpu: [arm64] os: [linux] libc: [glibc] - '@swc/core-linux-arm64-musl@1.15.30': - resolution: {integrity: sha512-1SYGs2l0Yyyi0pR/P/NKz/x0kqxkoiw+BXeJjLUdecSk/KasncWlJrc6hOvFSgKHOBrzgM5jwuluKtlT8dnrcA==} + '@swc/core-linux-arm64-musl@1.15.33': + resolution: {integrity: sha512-il7tYM+CpUNzieQbwAjFT1P8zqAhmGWNAGhQZBnxurXZ0aNn+5nqYFTEUKNZl7QibtT0uQXzTZrNGHCIj6Y1Og==} engines: {node: '>=10'} cpu: [arm64] os: [linux] libc: [musl] - '@swc/core-linux-ppc64-gnu@1.15.30': - resolution: {integrity: sha512-TXREtiXeRhbfDFbmhnkIsXpKfzbfT73YkV2ZF6w0sfxgjC5zI2ZAbaCOq25qxvegofj2K93DtOpm9RLaBgqR2g==} + '@swc/core-linux-ppc64-gnu@1.15.33': + resolution: {integrity: sha512-ZtNBwN0Z7CFj9Il0FcPaKdjgP7URyKu/3RfH46vq+0paOBqLj4NYldD6Qo//Duif/7IOtAraUfDOmp0PLAufog==} engines: {node: '>=10'} cpu: [ppc64] os: [linux] libc: [glibc] - '@swc/core-linux-s390x-gnu@1.15.30': - resolution: {integrity: sha512-DCR2YYeyd6DQE4OuDhImouuNcjXEiEdnn1Y0DyGteugPEDvVuvYk8Xddi+4o2SgWH6jiW8/I+3emZvbep1NC+g==} + '@swc/core-linux-s390x-gnu@1.15.33': + resolution: {integrity: sha512-De1IyajoOmhOYYjw/lx66bKlyDpHZTueqwpDrWgf5O7T6d1ODeJJO9/OqMBmrBQc5C+dNnlmIufHsp4QVCWufA==} engines: {node: '>=10'} cpu: [s390x] os: [linux] libc: [glibc] - '@swc/core-linux-x64-gnu@1.15.30': - resolution: {integrity: sha512-5Pizw3NgfOJ5BJOBK8TIRa59xFW2avESTOBDPTAYwZYa1JNDs+KMF9lUfjJiJLM5HiMs/wPheA9eiT0q9m2AoA==} + '@swc/core-linux-x64-gnu@1.15.33': + resolution: {integrity: sha512-mGTH0YxmUN+x6vRN/I6NOk5X0ogNktkwPnJ94IMvR7QjhRDwL0O8RXEDhyUM0YtwWrryBOqaJQBX4zruxEPRGw==} engines: {node: '>=10'} cpu: [x64] os: [linux] libc: [glibc] - '@swc/core-linux-x64-musl@1.15.30': - resolution: {integrity: sha512-qyqydP/wyH8alcIP4a2hnGSjHLJjm9H7yDFup+CPy9oTahFgLLwnNcv5UHXqO2Qs3AIND+cls5f/Bb6hqpxdgA==} + '@swc/core-linux-x64-musl@1.15.33': + resolution: {integrity: sha512-hj628ZkSEJf6zMf5VMbYrG2O6QqyTIp2qwY6VlCjvIa9lAEZ5c2lfPblCLVGYubTeLJDxadLB/CxqQYOQABeEQ==} engines: {node: '>=10'} cpu: [x64] os: [linux] libc: [musl] - '@swc/core-win32-arm64-msvc@1.15.30': - resolution: {integrity: sha512-CaQENgDHVGOg1mSF5sQVgvfFHG9kjMor2rkLMLeLOkfZYNj13ppnJ9+lfaBZLZUMMbnlGQnavCJb8PVBUOso7Q==} + '@swc/core-win32-arm64-msvc@1.15.33': + resolution: {integrity: sha512-GV2oohtN2/5+KSccl86VULu3aT+LrISC8uzgSq0FRnikpD+Zwc+sBlXmoKQ+Db6jI57ITUOIB8jRkdGMABC29g==} engines: {node: '>=10'} cpu: [arm64] os: [win32] - '@swc/core-win32-ia32-msvc@1.15.30': - resolution: {integrity: sha512-30VdLeGk6fugiUs/kUdJ/pAg7z/zpvVbR11RH60jZ0Z42WIeIniYx0rLEWN7h/pKJ3CopqsQ3RsogCAkRKiA2g==} + '@swc/core-win32-ia32-msvc@1.15.33': + resolution: {integrity: sha512-gtyvzSNR8DHKfFEA2uqb8Ld1myqi6uEg2jyeUq3ikn5ytYs7H8RpZYC8mdy4NXr8hfcdJfCLXPlYaqqfBXpoEQ==} engines: {node: '>=10'} cpu: [ia32] os: [win32] - '@swc/core-win32-x64-msvc@1.15.30': - resolution: {integrity: sha512-4iObHPR+Q4oDY110EF5SF5eIaaVJNpMdG9C0q3Q92BsJ5y467uHz7sYQhP60WYlLFsLQ1el2YrIPUItUAQGOKg==} + '@swc/core-win32-x64-msvc@1.15.33': + resolution: {integrity: sha512-d6fRqQSkJI+kmMEBWaDQ7TMl8+YjLYbwRUPZQ9DY0ORBJeTzOrG0twvfvlZ2xgw6jA0ScQKgfBm4vHLSLl5Hqg==} engines: {node: '>=10'} cpu: [x64] os: [win32] - '@swc/core@1.15.30': - resolution: {integrity: sha512-R8VQbQY1BZcbIF2p3gjlTCwAQzx1A194ugWfwld5y+WgVVWqVKm7eURGGOVbQVubgKWzidP2agomBbg96rZilQ==} + '@swc/core@1.15.33': + resolution: {integrity: sha512-jOlwnFV2xhuuZeAUILGFULeR6vDPfijEJ57evfocwznQldLU3w2cZ9bSDryY9ip+AsM3r1NJKzf47V2NXebkeQ==} engines: {node: '>=10'} peerDependencies: '@swc/helpers': '>=0.5.17' @@ -4782,8 +4987,8 @@ packages: '@swc/counter@0.1.3': resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} - '@swc/helpers@0.5.21': - resolution: {integrity: sha512-jI/VAmtdjB/RnI8GTnokyX7Ug8c+g+ffD6QRLa6XQewtnGyukKkKSk3wLTM3b5cjt1jNh9x0jfVlagdN2gDKQg==} + '@swc/helpers@0.5.23': + resolution: {integrity: sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==} '@swc/types@0.1.26': resolution: {integrity: sha512-lyMwd7WGgG79RS7EERZV3T8wMdmPq3xwyg+1nmAM64kIhx5yl+juO2PYIHb7vTiPgPCj8LYjsNV2T5wiQHUEaw==} @@ -4792,69 +4997,69 @@ packages: resolution: {integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==} engines: {node: '>=14.16'} - '@tailwindcss/node@4.2.4': - resolution: {integrity: sha512-Ai7+yQPxz3ddrDQzFfBKdHEVBg0w3Zl83jnjuwxnZOsnH9pGn93QHQtpU0p/8rYWxvbFZHneni6p1BSLK4DkGA==} + '@tailwindcss/node@4.3.0': + resolution: {integrity: sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g==} - '@tailwindcss/oxide-android-arm64@4.2.4': - resolution: {integrity: sha512-e7MOr1SAn9U8KlZzPi1ZXGZHeC5anY36qjNwmZv9pOJ8E4Q6jmD1vyEHkQFmNOIN7twGPEMXRHmitN4zCMN03g==} + '@tailwindcss/oxide-android-arm64@4.3.0': + resolution: {integrity: sha512-TJPiq67tKlLuObP6RkwvVGDoxCMBVtDgKkLfa/uyj7/FyxvQwHS+UOnVrXXgbEsfUaMgiVvC4KbJnRr26ho4Ng==} engines: {node: '>= 20'} cpu: [arm64] os: [android] - '@tailwindcss/oxide-darwin-arm64@4.2.4': - resolution: {integrity: sha512-tSC/Kbqpz/5/o/C2sG7QvOxAKqyd10bq+ypZNf+9Fi2TvbVbv1zNpcEptcsU7DPROaSbVgUXmrzKhurFvo5eDg==} + '@tailwindcss/oxide-darwin-arm64@4.3.0': + resolution: {integrity: sha512-oMN/WZRb+SO37BmUElEgeEWuU8E/HXRkiODxJxLe1UTHVXLrdVSgfaJV7pSlhRGMSOiXLuxTIjfsF3wYvz8cgQ==} engines: {node: '>= 20'} cpu: [arm64] os: [darwin] - '@tailwindcss/oxide-darwin-x64@4.2.4': - resolution: {integrity: sha512-yPyUXn3yO/ufR6+Kzv0t4fCg2qNr90jxXc5QqBpjlPNd0NqyDXcmQb/6weunH/MEDXW5dhyEi+agTDiqa3WsGg==} + '@tailwindcss/oxide-darwin-x64@4.3.0': + resolution: {integrity: sha512-N6CUmu4a6bKVADfw77p+iw6Yd9Q3OBhe0veaDX+QazfuVYlQsHfDgxBrsjQ/IW+zywL8mTrNd0SdJT/zgtvMdA==} engines: {node: '>= 20'} cpu: [x64] os: [darwin] - '@tailwindcss/oxide-freebsd-x64@4.2.4': - resolution: {integrity: sha512-BoMIB4vMQtZsXdGLVc2z+P9DbETkiopogfWZKbWwM8b/1Vinbs4YcUwo+kM/KeLkX3Ygrf4/PsRndKaYhS8Eiw==} + '@tailwindcss/oxide-freebsd-x64@4.3.0': + resolution: {integrity: sha512-zDL5hBkQdH5C6MpqbK3gQAgP80tsMwSI26vjOzjJtNCMUo0lFgOItzHKBIupOZNQxt3ouPH7RPhvNhiTfCe5CQ==} engines: {node: '>= 20'} cpu: [x64] os: [freebsd] - '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.4': - resolution: {integrity: sha512-7pIHBLTHYRAlS7V22JNuTh33yLH4VElwKtB3bwchK/UaKUPpQ0lPQiOWcbm4V3WP2I6fNIJ23vABIvoy2izdwA==} + '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.0': + resolution: {integrity: sha512-R06HdNi7A7OEoMsf6d4tjZ71RCWnZQPHj2mnotSFURjNLdBC+cIgXQ7l81CqeoiQftjf6OOblxXMInMgN2VzMA==} engines: {node: '>= 20'} cpu: [arm] os: [linux] - '@tailwindcss/oxide-linux-arm64-gnu@4.2.4': - resolution: {integrity: sha512-+E4wxJ0ZGOzSH325reXTWB48l42i93kQqMvDyz5gqfRzRZ7faNhnmvlV4EPGJU3QJM/3Ab5jhJ5pCRUsKn6OQw==} + '@tailwindcss/oxide-linux-arm64-gnu@4.3.0': + resolution: {integrity: sha512-qTJHELX8jetjhRQHCLilkVLmybpzNQAtaI/gaoVoidn/ufbNDbAo8KlK2J+yPoc8wQxvDxCmh/5lr8nC1+lTbg==} engines: {node: '>= 20'} cpu: [arm64] os: [linux] libc: [glibc] - '@tailwindcss/oxide-linux-arm64-musl@4.2.4': - resolution: {integrity: sha512-bBADEGAbo4ASnppIziaQJelekCxdMaxisrk+fB7Thit72IBnALp9K6ffA2G4ruj90G9XRS2VQ6q2bCKbfFV82g==} + '@tailwindcss/oxide-linux-arm64-musl@4.3.0': + resolution: {integrity: sha512-Z6sukiQsngnWO+l39X4pPbiWT81IC+PLKF+PHxIlyZbGNb9MODfYlXEVlFvej5BOZInWX01kVyzeLvHsXhfczQ==} engines: {node: '>= 20'} cpu: [arm64] os: [linux] libc: [musl] - '@tailwindcss/oxide-linux-x64-gnu@4.2.4': - resolution: {integrity: sha512-7Mx25E4WTfnht0TVRTyC00j3i0M+EeFe7wguMDTlX4mRxafznw0CA8WJkFjWYH5BlgELd1kSjuU2JiPnNZbJDA==} + '@tailwindcss/oxide-linux-x64-gnu@4.3.0': + resolution: {integrity: sha512-DRNdQRpSGzRGfARVuVkxvM8Q12nh19l4BF/G7zGA1oe+9wcC6saFBHTISrpIcKzhiXtSrlSrluCfvMuledoCTQ==} engines: {node: '>= 20'} cpu: [x64] os: [linux] libc: [glibc] - '@tailwindcss/oxide-linux-x64-musl@4.2.4': - resolution: {integrity: sha512-2wwJRF7nyhOR0hhHoChc04xngV3iS+akccHTGtz965FwF0up4b2lOdo6kI1EbDaEXKgvcrFBYcYQQ/rrnWFVfA==} + '@tailwindcss/oxide-linux-x64-musl@4.3.0': + resolution: {integrity: sha512-Z0IADbDo8bh6I7h2IQMx601AdXBLfFpEdUotft86evd/8ZPflZe9COPO8Q1vw+pfLWIUo9zN/JGZvwuAJqduqg==} engines: {node: '>= 20'} cpu: [x64] os: [linux] libc: [musl] - '@tailwindcss/oxide-wasm32-wasi@4.2.4': - resolution: {integrity: sha512-FQsqApeor8Fo6gUEklzmaa9994orJZZDBAlQpK2Mq+DslRKFJeD6AjHpBQ0kZFQohVr8o85PPh8eOy86VlSCmw==} + '@tailwindcss/oxide-wasm32-wasi@4.3.0': + resolution: {integrity: sha512-HNZGOUxEmElksYR7S6sC5jTeNGpobAsy9u7Gu0AskJ8/20FR9GqebUyB+HBcU/ax6BHuiuJi+Oda4B+YX6H1yA==} engines: {node: '>=14.0.0'} cpu: [wasm32] bundledDependencies: @@ -4865,24 +5070,24 @@ packages: - '@emnapi/wasi-threads' - tslib - '@tailwindcss/oxide-win32-arm64-msvc@4.2.4': - resolution: {integrity: sha512-L9BXqxC4ToVgwMFqj3pmZRqyHEztulpUJzCxUtLjobMCzTPsGt1Fa9enKbOpY2iIyVtaHNeNvAK8ERP/64sqGQ==} + '@tailwindcss/oxide-win32-arm64-msvc@4.3.0': + resolution: {integrity: sha512-Pe+RPVTi1T+qymuuRpcdvwSVZjnll/f7n8gBxMMh3xLTctMDKqpdfGimbMyioqtLhUYZxdJ9wGNhV7MKHvgZsQ==} engines: {node: '>= 20'} cpu: [arm64] os: [win32] - '@tailwindcss/oxide-win32-x64-msvc@4.2.4': - resolution: {integrity: sha512-ESlKG0EpVJQwRjXDDa9rLvhEAh0mhP1sF7sap9dNZT0yyl9SAG6T7gdP09EH0vIv0UNTlo6jPWyujD6559fZvw==} + '@tailwindcss/oxide-win32-x64-msvc@4.3.0': + resolution: {integrity: sha512-Mvrf2kXW/yeW/OTezZlCGOirXRcUuLIBx/5Y12BaPM7wJoryG6dfS/NJL8aBPqtTEx/Vm4T4vKzFUcKDT+TKUA==} engines: {node: '>= 20'} cpu: [x64] os: [win32] - '@tailwindcss/oxide@4.2.4': - resolution: {integrity: sha512-9El/iI069DKDSXwTvB9J4BwdO5JhRrOweGaK25taBAvBXyXqJAX+Jqdvs8r8gKpsI/1m0LeJLyQYTf/WLrBT1Q==} + '@tailwindcss/oxide@4.3.0': + resolution: {integrity: sha512-F7HZGBeN9I0/AuuJS5PwcD8xayx5ri5GhjYUDBEVYUkexyA/giwbDNjRVrxSezE3T250OU2K/wp/ltWx3UOefg==} engines: {node: '>= 20'} - '@tailwindcss/vite@4.2.4': - resolution: {integrity: sha512-pCvohwOCspk3ZFn6eJzrrX3g4n2JY73H6MmYC87XfGPyTty4YsCjYTMArRZm/zOI8dIt3+EcrLHAFPe5A4bgtw==} + '@tailwindcss/vite@4.3.0': + resolution: {integrity: sha512-t6J3OrB5Fc0ExuhohouH0fWUGMYL6PTLhW+E7zIk/pdbnJARZDCwjBznFnkh5ynRnIRSI4YjtTH0t6USjJISrw==} peerDependencies: vite: ^5.2.0 || ^6 || ^7 || ^8 @@ -4945,21 +5150,17 @@ packages: svelte: optional: true - '@trysound/sax@0.2.0': - resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} - engines: {node: '>=10.13.0'} + '@turf/boolean-point-in-polygon@7.3.5': + resolution: {integrity: sha512-ba7+B0wzaS9GtERZOoXUZ6oW8IcIJHNQZf3c+tiD9ESjcsPO1Q/4qIJGTKl92nBLhhracHJxMWBM/U6hAVkaRg==} - '@turf/boolean-point-in-polygon@7.3.4': - resolution: {integrity: sha512-v/4hfyY90Vz9cDgs2GwjQf+Lft8o7mNCLJOTz/iv8SHAIgMMX0czEoIaNVOJr7tBqPqwin1CGwsncrkf5C9n8Q==} + '@turf/helpers@7.3.5': + resolution: {integrity: sha512-E/NMGV5MwbjjP7AJXBtsanC3yY8N2MQ87IGdIgkB2ji5AtBpwnH4L3gEqpYN4RlCJJWbLbzO91BbKv2waUd0eg==} - '@turf/helpers@7.3.4': - resolution: {integrity: sha512-U/S5qyqgx3WTvg4twaH0WxF3EixoTCfDsmk98g1E3/5e2YKp7JKYZdz0vivsS5/UZLJeZDEElOSFH4pUgp+l7g==} + '@turf/invariant@7.3.5': + resolution: {integrity: sha512-ZVIvsBvjr8lO7WxC5zYNjRsjSDvyGvWkJMjuWaJjTU8x+1tmfNnw3gDX/TI2Sit83gcRYLYkNo23lB/udqx/Hg==} - '@turf/invariant@7.3.4': - resolution: {integrity: sha512-88Eo4va4rce9sNZs6XiMJowWkikM3cS2TBhaCKlU+GFHdNf8PFEpiU42VDU8q5tOF6/fu21Rvlke5odgOGW4AQ==} - - '@tybys/wasm-util@0.10.1': - resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} + '@tybys/wasm-util@0.10.2': + resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} '@types/accepts@1.3.7': resolution: {integrity: sha512-Pay9fq2lM2wXPWbteBsRAGiWH2hig4ZE2asK+mm7kUzlxRTfL961rj89I6zV/E3PcIkDqyuBEcMxFT7rccugeQ==} @@ -4991,8 +5192,8 @@ packages: '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} - '@types/chrome@0.1.32': - resolution: {integrity: sha512-n5Cqlh7zyAqRLQWLXkeV5K/1BgDZdVcO/dJSTa8x+7w+sx7m73UrDmduAptg4KorMtyTW2TNnPu8RGeaDMKNGg==} + '@types/chrome@0.1.42': + resolution: {integrity: sha512-tdT2roFqGecZZDjA9fUEAINb2STxSPifHMDvY6EfRjNRCjdrs/0FwKt5RCIA9MKMd1arAYZZL3nwEkp6ZLZu2w==} '@types/chromecast-caf-sender@1.0.11': resolution: {integrity: sha512-Pv3xvNYtxD/cTM/tKfuZRlLasvpxAm+CFni0GJd6Cp8XgiZS9g9tMZkR1uymsi5fIFv057SZKKAWVFFgy7fJtw==} @@ -5122,8 +5323,8 @@ packages: '@types/d3@7.4.3': resolution: {integrity: sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==} - '@types/debug@4.1.12': - resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + '@types/debug@4.1.13': + resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} '@types/deep-eql@4.0.2': resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} @@ -5152,11 +5353,14 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - '@types/express-serve-static-core@4.19.7': - resolution: {integrity: sha512-FvPtiIf1LfhzsaIXhv/PHan/2FeQBbtBDtfX2QfvPxdUelMDEckK08SM6nqo1MIZY3RUlfA+HV8+hFUSio78qg==} + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} - '@types/express-serve-static-core@5.1.0': - resolution: {integrity: sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA==} + '@types/express-serve-static-core@4.19.8': + resolution: {integrity: sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA==} + + '@types/express-serve-static-core@5.1.1': + resolution: {integrity: sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==} '@types/express@4.17.25': resolution: {integrity: sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==} @@ -5197,8 +5401,8 @@ packages: '@types/http-assert@1.5.6': resolution: {integrity: sha512-TTEwmtjgVbYAzZYWyeHPrrtWnfVkm8tQkP8P21uQifPgMRgjrow3XDEYqucuC8SKZJT7pUnhU/JymvjggxO9vw==} - '@types/http-cache-semantics@4.0.4': - resolution: {integrity: sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==} + '@types/http-cache-semantics@4.2.0': + resolution: {integrity: sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==} '@types/http-errors@2.0.5': resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} @@ -5236,8 +5440,8 @@ packages: '@types/koa-compose@3.2.9': resolution: {integrity: sha512-BroAZ9FTvPiCy0Pi8tjD1OfJ7bgU1gQf0eR6e1Vm+JJATy9eKOG3hQMFtMciMawiSOVnLMdmUOC46s7HBhSTsA==} - '@types/koa@3.0.1': - resolution: {integrity: sha512-VkB6WJUQSe0zBpR+Q7/YIUESGp5wPHcaXr0xueU5W0EOUWtlSbblsl+Kl31lyRQ63nIILh0e/7gXjQ09JXJIHw==} + '@types/koa@3.0.3': + resolution: {integrity: sha512-TdtNEJ7sYSrFQcVuS2ySsVqnq5EyE3oJbnfFJvkC9UtGP4Kpem5KE7r+ivHIbIAQAofSqnlB5D3vkfYO69TQpg==} '@types/leaflet@1.9.21': resolution: {integrity: sha512-TbAd9DaPGSnzp6QvtYngntMZgcRk+igFELwR2N99XZn7RXUdKgsXMR+28bUO0rPsWp8MIu/f47luLIQuSLYv/w==} @@ -5275,20 +5479,14 @@ packages: '@types/multer@2.1.0': resolution: {integrity: sha512-zYZb0+nJhOHtPpGDb3vqPjwpdeGlGC157VpkqNQL+UU2qwoacoQ7MpsAmUptI/0Oa127X32JzWDqQVEXp2RcIA==} - '@types/node-forge@1.3.14': - resolution: {integrity: sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==} - '@types/node@17.0.45': resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==} '@types/node@18.19.130': resolution: {integrity: sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==} - '@types/node@24.12.2': - resolution: {integrity: sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==} - - '@types/node@25.6.0': - resolution: {integrity: sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==} + '@types/node@24.12.4': + resolution: {integrity: sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA==} '@types/nodemailer@8.0.0': resolution: {integrity: sha512-fyf8jWULsCo0d0BuoQ75i6IeoHs47qcqxWc7yUdUcV0pOZGjUTTOvwdG1PRXUDqN/8A64yQdQdnA2pZgcdi+cA==} @@ -5314,14 +5512,14 @@ packages: '@types/pngjs@6.0.5': resolution: {integrity: sha512-0k5eKfrA83JOZPppLtS2C7OUtyNAl2wKNxfyYl9Q5g9lPkgBl/9hNyAu6HuEH2J4XmIv2znEpkDd0SaZVxW6iQ==} - '@types/prismjs@1.26.5': - resolution: {integrity: sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==} + '@types/prismjs@1.26.6': + resolution: {integrity: sha512-vqlvI7qlMvcCBbVe0AKAb4f97//Hy0EBTaiW8AalRnG/xAN5zOiWWyrNqNXeq8+KAuvRewjCVY1+IPxk4RdNYw==} '@types/qrcode@1.5.6': resolution: {integrity: sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==} - '@types/qs@6.14.0': - resolution: {integrity: sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==} + '@types/qs@6.15.1': + resolution: {integrity: sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==} '@types/range-parser@1.2.7': resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} @@ -5335,8 +5533,8 @@ packages: '@types/react-router@5.1.20': resolution: {integrity: sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==} - '@types/react@19.2.14': - resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} + '@types/react@19.2.15': + resolution: {integrity: sha512-eRwcGNHve+E8qtEQSSRl6urh+rFop4v8gm6O8rGv25CodbvFdLjA1vVQ1KkiFE0w0UPOnb8tDiFKL5lp0rtY5Q==} '@types/readdir-glob@1.1.5': resolution: {integrity: sha512-raiuEPUYqXu+nvtY2Pe8s8FEmZ3x5yAH4VkLdihcPdalvsHltomrRC9BzuStrJ9yk06470hS0Crw0f1pXqD+Hg==} @@ -5377,8 +5575,8 @@ packages: '@types/ssh2@1.15.5': resolution: {integrity: sha512-N1ASjp/nXH3ovBHddRJpli4ozpk6UdDYIX4RJWFa9L1YKnzdhTlVmiGHm4DZnj/jLbqZpes4aeR30EFGQtvhQQ==} - '@types/superagent@8.1.9': - resolution: {integrity: sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ==} + '@types/superagent@8.1.10': + resolution: {integrity: sha512-nbt4IWXABhW0jGmmpRzCFNlbmwCTzZ2gTUsNIr+X+ItdqPms+PAJZbWsNzpS2USqXjcoNLQcO6nXo60zcPQiIg==} '@types/supercluster@7.1.3': resolution: {integrity: sha512-Z0pOY34GDFl3Q6hUFYf3HkTwKEE02e7QgtJppBt+beEAxnyOpJua+voGFvxINBHa06GwLFFym7gRPY2SiKIfIA==} @@ -5416,91 +5614,81 @@ packages: '@types/yargs@17.0.35': resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==} - '@typescript-eslint/eslint-plugin@8.59.0': - resolution: {integrity: sha512-HyAZtpdkgZwpq8Sz3FSUvCR4c+ScbuWa9AksK2Jweub7w4M3yTz4O11AqVJzLYjy/B9ZWPyc81I+mOdJU/bDQw==} + '@typescript-eslint/eslint-plugin@8.59.4': + resolution: {integrity: sha512-PegsU+XfyJJNjd4+u/k6f9yTyp0lEXXiPopUNobZcIAUJFGICFLN+sP0Rb3JehVmiij1Ph0dFGYqODoRo/2+6A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.59.0 + '@typescript-eslint/parser': ^8.59.4 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/parser@8.59.0': - resolution: {integrity: sha512-TI1XGwKbDpo9tRW8UDIXCOeLk55qe9ZFGs8MTKU6/M08HWTw52DD/IYhfQtOEhEdPhLMT26Ka/x7p70nd3dzDg==} + '@typescript-eslint/parser@8.59.4': + resolution: {integrity: sha512-zORHqO/tuhxY1zWuTvMUqddRxpiFJ72xVfcNoWpqdLjs6lfPbuQBJuW4pk+49/uBMy7Ssr4bzgjiKmmDB1UbZQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/project-service@8.59.0': - resolution: {integrity: sha512-Lw5ITrR5s5TbC19YSvlr63ZfLaJoU6vtKTHyB0GQOpX0W7d5/Ir6vUahWi/8Sps/nOukZQ0IB3SmlxZnjaKVnw==} + '@typescript-eslint/project-service@8.59.4': + resolution: {integrity: sha512-Ly00Vu4oAacfDeHp2Zg85ioNG6l8HG+tN1D7J+xTHSxu9y0awYKJ2zH1rFBn8ZSfuGK+7FxK3Cgl3uAz0aZZLg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/scope-manager@8.59.0': - resolution: {integrity: sha512-UzR16Ut8IpA3Mc4DbgAShlPPkVm8xXMWafXxB0BocaVRHs8ZGakAxGRskF7FId3sdk9lgGD73GSFaWmWFDE4dg==} + '@typescript-eslint/scope-manager@8.59.4': + resolution: {integrity: sha512-mUeR/3H1WrTAddJrwut8OoPjfauaztMQmRwV5fQTUyNVJCLiUXXe4lGEyYIL2oFDpP7UtgbGJXCt72wT0z2S3Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.59.0': - resolution: {integrity: sha512-91Sbl3s4Kb3SybliIY6muFBmHVv+pYXfybC4Oolp3dvk8BvIE3wOPc+403CWIT7mJNkfQRGtdqghzs2+Z91Tqg==} + '@typescript-eslint/tsconfig-utils@8.59.4': + resolution: {integrity: sha512-DLCpnKgD4alVxTBSKulK+gU1KCqOgUXfDRDXh2mZgzokQKa/70ax93I2uVO3m/LLvIAtWZIFoiifudmIqAxpMA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/type-utils@8.59.0': - resolution: {integrity: sha512-3TRiZaQSltGqGeNrJzzr1+8YcEobKH9rHnqIp/1psfKFmhRQDNMGP5hBufanYTGznwShzVLs3Mz+gDN7HkWfXg==} + '@typescript-eslint/type-utils@8.59.4': + resolution: {integrity: sha512-uonTuPAAKr9XaBGqJ3LjYTh72zy5DyGesljO9gtmk/eFW0W1fRHjnwVYKB35Lm8d5Q5CluEW3gPHjTvZTmgrfA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/types@8.59.0': - resolution: {integrity: sha512-nLzdsT1gdOgFxxxwrlNVUBzSNBEEHJ86bblmk4QAS6stfig7rcJzWKqCyxFy3YRRHXDWEkb2NralA1nOYkkm/A==} + '@typescript-eslint/types@8.59.4': + resolution: {integrity: sha512-F1o7WJcCq+bc8dwcO/YsSEOudAH8RDtaOhM6wcAQhcUsFhnWQl81JKy48q1hoxAU0qrzM89+31GYh1515Zde3Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.59.0': - resolution: {integrity: sha512-O9Re9P1BmBLFJyikRbQpLku/QA3/AueZNO9WePLBwQrvkixTmDe8u76B6CYUAITRl/rHawggEqUGn5QIkVRLMw==} + '@typescript-eslint/typescript-estree@8.59.4': + resolution: {integrity: sha512-F+RuOmcDXo4+TPdfd/TCLS3m2nw8gE9XXyZLrA3JBfaA5tz9TtdkyD3YJFmPxulyc2cKbEok/CvFE3MgSLWnag==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/utils@8.59.0': - resolution: {integrity: sha512-I1R/K7V07XsMJ12Oaxg/O9GfrysGTmCRhvZJBv0RE0NcULMzjqVpR5kRRQjHsz3J/bElU7HwCO7zkqL+MSUz+g==} + '@typescript-eslint/utils@8.59.4': + resolution: {integrity: sha512-cYXeNAUsG4lJo5dbc1FcKm+JwIWrj1/UpTORsC6tGMjEZ81DYcvIr9/ueikhMa/Y/gDQYGp+YX9/xQrXje5BJw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/visitor-keys@8.59.0': - resolution: {integrity: sha512-/uejZt4dSere1bx12WLlPfv8GktzcaDtuJ7s42/HEZ5zGj9oxRaD4bj7qwSunXkf+pbAhFt2zjpHYUiT5lHf0Q==} + '@typescript-eslint/visitor-keys@8.59.4': + resolution: {integrity: sha512-U3gxVaDVnuZKhSspW/MzMxE1kq7zOdc072FcSNoqA1I9p8HyKbBFfEHoWckBAMgNMph4MamwS5iTVzFmrnt8TQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@ungap/structured-clone@1.3.0': - resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + '@ungap/structured-clone@1.3.1': + resolution: {integrity: sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==} - '@valibot/to-json-schema@1.6.0': - resolution: {integrity: sha512-d6rYyK5KVa2XdqamWgZ4/Nr+cXhxjy7lmpe6Iajw15J/jmU+gyxl2IEd1Otg1d7Rl3gOQL5reulnSypzBtYy1A==} + '@upsetjs/venn.js@2.0.0': + resolution: {integrity: sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==} + + '@valibot/to-json-schema@1.7.0': + resolution: {integrity: sha512-Y3pPVibbIOHzohrlxSINvO7w/bvXkoYS3BQHoImV9ynE+bXKf171bdMucPurV2zp7gdmt0L1HCcNAsbo7cFRQw==} peerDependencies: - valibot: ^1.3.0 + valibot: ^1.4.0 - '@vercel/oidc@3.0.5': - resolution: {integrity: sha512-fnYhv671l+eTTp48gB4zEsTW/YtRgRPnkI2nT7x6qw5rkI1Lq2hTmQIpHPgyThI0znLK+vX2n9XxKdXZ7BUbbw==} - engines: {node: '>= 20'} - - '@vitest/coverage-v8@3.2.4': - resolution: {integrity: sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==} + '@vitest/coverage-v8@4.1.7': + resolution: {integrity: sha512-qsYPeXc5Q9dFLd1i8Ap+Bx8sQgcp+rFVQo4R0dDsWNBzl26ldVF1qOO+RL24K7FDrR6pA+50XedRLSoSG24bVQ==} peerDependencies: - '@vitest/browser': 3.2.4 - vitest: 3.2.4 - peerDependenciesMeta: - '@vitest/browser': - optional: true - - '@vitest/coverage-v8@4.1.5': - resolution: {integrity: sha512-38C0/Ddb7HcRG0Z4/DUem8x57d2p9jYgp18mkaYswEOQBGsI1CG4f/hjm0ZCeaJfWhSZ4k7jgs29V1Zom7Ki9A==} - peerDependencies: - '@vitest/browser': 4.1.5 - vitest: 4.1.5 + '@vitest/browser': 4.1.7 + vitest: 4.1.7 peerDependenciesMeta: '@vitest/browser': optional: true @@ -5508,8 +5696,8 @@ packages: '@vitest/expect@3.2.4': resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} - '@vitest/expect@4.1.5': - resolution: {integrity: sha512-PWBaRY5JoKuRnHlUHfpV/KohFylaDZTupcXN1H9vYryNLOnitSw60Mw9IAE2r67NbwwzBw/Cc/8q9BK3kIX8Kw==} + '@vitest/expect@4.1.7': + resolution: {integrity: sha512-1R+tw0ortHEbZDGMymm+pN7/AFQ/RkFFdtd7EN+VBpynKmLbP8A3rpEXdshBJ7+8hQ9zBJh/i1s0yKNtxAnU7w==} '@vitest/mocker@3.2.4': resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} @@ -5522,8 +5710,8 @@ packages: vite: optional: true - '@vitest/mocker@4.1.5': - resolution: {integrity: sha512-/x2EmFC4mT4NNzqvC3fmesuV97w5FC903KPmey4gsnJiMQ3Be1IlDKVaDaG8iqaLFHqJ2FVEkxZk5VmeLjIItw==} + '@vitest/mocker@4.1.7': + resolution: {integrity: sha512-vY7nuamKgfvpA1Koa3oYIw/k7D6kZnpGyNMZW8loow2bsBYla1TFdqTaXncWdRn4pgwNs+90RhnXhJScDwQeJA==} peerDependencies: msw: ^2.4.9 vite: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -5536,32 +5724,32 @@ packages: '@vitest/pretty-format@3.2.4': resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} - '@vitest/pretty-format@4.1.5': - resolution: {integrity: sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g==} + '@vitest/pretty-format@4.1.7': + resolution: {integrity: sha512-umgCarTOYQWIaDMvGDRZij+6b9oVeLIyJzfN+AS88e0ZOU3QTgNNSTtjQOpcvWr3np1N0j4WgZj+sb3oYBDscw==} '@vitest/runner@3.2.4': resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==} - '@vitest/runner@4.1.5': - resolution: {integrity: sha512-2D+o7Pr82IEO46YPpoA/YU0neeyr6FTerQb5Ro7BUnBuv6NQtT/kmVnczngiMEBhzgqz2UZYl5gArejsyERDSQ==} + '@vitest/runner@4.1.7': + resolution: {integrity: sha512-BapjmAQ2aI78WdMEfeUWivnfVzB+VPGwWRQcJE0OUq7qEeEcBsCSf+0T5iREBNE5nBb4wA5Ya0W6IA+sghdEFw==} '@vitest/snapshot@3.2.4': resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} - '@vitest/snapshot@4.1.5': - resolution: {integrity: sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ==} + '@vitest/snapshot@4.1.7': + resolution: {integrity: sha512-ZacLzja+TmJeZ1h14xW2FB/WpeimUD3haBXQPyJqxvo8jQTmfeA8zv58mtjN2C7EHXZDYVcVYdYmAxjkWVvKCw==} '@vitest/spy@3.2.4': resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} - '@vitest/spy@4.1.5': - resolution: {integrity: sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ==} + '@vitest/spy@4.1.7': + resolution: {integrity: sha512-kbkI5LMWakyuTIvs6fUJ5qdIVb1XVKsYJAT4OJ938cHMROYMSfmoQdZy0aaAnjbbc8F61vkoTqz/Az+/HiIu5Q==} '@vitest/utils@3.2.4': resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} - '@vitest/utils@4.1.5': - resolution: {integrity: sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==} + '@vitest/utils@4.1.7': + resolution: {integrity: sha512-T532WBu791cBxJlCl6SO+J14l81DQx6uQHm1bQbmCDY7nqlEIgkza/UFnSBNaUtSf41unldDFjdOBYEQC4b5Hw==} '@webassemblyjs/ast@1.14.1': resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==} @@ -5657,8 +5845,8 @@ packages: peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - acorn-walk@8.3.4: - resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} + acorn-walk@8.3.5: + resolution: {integrity: sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==} engines: {node: '>=0.4.0'} acorn@8.16.0: @@ -5682,12 +5870,6 @@ packages: resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} engines: {node: '>=8'} - ai@5.0.113: - resolution: {integrity: sha512-26vivpSO/mzZj0k1Si2IpsFspp26ttQICHRySQiMrtWcRd5mnJMX2a8sG28vmZ38C+JUn1cWmfZrsLMxkSMw9g==} - engines: {node: '>=18'} - peerDependencies: - zod: ^3.25.76 || ^4.1.8 - ajv-formats@2.1.1: resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} peerDependencies: @@ -5714,19 +5896,22 @@ packages: peerDependencies: ajv: ^8.8.2 - ajv@6.14.0: - resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} + ajv@6.15.0: + resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} ajv@8.18.0: resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} - algoliasearch-helper@3.26.1: - resolution: {integrity: sha512-CAlCxm4fYBXtvc5MamDzP6Svu8rW4z9me4DCBY1rQ2UDJ0u0flWmusQ8M3nOExZsLLRcUwUPoRAPMrhzOG3erw==} + ajv@8.20.0: + resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} + + algoliasearch-helper@3.29.1: + resolution: {integrity: sha512-6ck2YFudF2Pje7szQoPBiRFTGfd+1I+0I/WfLPGn0bj1kvrFoOQmNyedNiDxTk3/r4IfSLDYk+RA4G7u8H6+yA==} peerDependencies: algoliasearch: '>= 3.1 < 6' - algoliasearch@5.46.0: - resolution: {integrity: sha512-7ML6fa2K93FIfifG3GMWhDEwT5qQzPTmoHKCTvhzGEwdbQ4n0yYUWZlLYT75WllTGJCJtNUI0C1ybN4BCegqvg==} + algoliasearch@5.52.1: + resolution: {integrity: sha512-fHA8+kXTbjagw3jkLiaS7KKrH8qe2DyOsiUhGlN4cdT77PEsfqXZl7ewDk1hsg+pJnPlnE50XtLxjR91iJOpmg==} engines: {node: '>= 14.0.0'} ansi-align@3.0.1: @@ -5838,6 +6023,10 @@ packages: asn1@0.2.6: resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==} + asn1js@3.0.10: + resolution: {integrity: sha512-S2s3aOytiKdFRdulw2qPE51MzjzVOisppcVv7jVFR+Kw0kxwvFrDcYA0h7Ndqbmj0HkMIXYWaoj7fli8kgx1eg==} + engines: {node: '>=12.0.0'} + assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} @@ -5845,9 +6034,6 @@ packages: ast-metadata-inferer@0.8.1: resolution: {integrity: sha512-ht3Dm6Zr7SXv6t1Ra6gFo0+kLDglHGrEbYihTkcycrbHw7WCcuhBzPlJYHEsIpycaUwzsJHje+vUcxXUX4ztTA==} - ast-v8-to-istanbul@0.3.12: - resolution: {integrity: sha512-BRRC8VRZY2R4Z4lFIL35MwNXmwVqBityvOIwETtsCSwvjl0IdgFsy9NhdaA6j74nUdtJJlIypeRhpDam19Wq3g==} - ast-v8-to-istanbul@1.0.0: resolution: {integrity: sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==} @@ -5884,8 +6070,8 @@ packages: resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} engines: {node: '>= 0.4'} - b4a@1.8.0: - resolution: {integrity: sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg==} + b4a@1.8.1: + resolution: {integrity: sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==} peerDependencies: react-native-b4a: '*' peerDependenciesMeta: @@ -5902,8 +6088,8 @@ packages: babel-plugin-dynamic-import-node@2.3.3: resolution: {integrity: sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==} - babel-plugin-polyfill-corejs2@0.4.14: - resolution: {integrity: sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==} + babel-plugin-polyfill-corejs2@0.4.17: + resolution: {integrity: sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==} peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 @@ -5912,8 +6098,13 @@ packages: peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - babel-plugin-polyfill-regenerator@0.6.5: - resolution: {integrity: sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg==} + babel-plugin-polyfill-corejs3@0.14.2: + resolution: {integrity: sha512-coWpDLJ410R781Npmn/SIBZEsAetR4xVi0SxLMXPaMO4lSf1MwnkGYMtkFxew0Dn8B3/CpbpYxN0JCgg8mn67g==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-polyfill-regenerator@0.6.8: + resolution: {integrity: sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==} peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 @@ -5930,8 +6121,8 @@ packages: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} - bare-events@2.8.2: - resolution: {integrity: sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==} + bare-events@2.8.3: + resolution: {integrity: sha512-HdUm8EMQBLaJvGUdidNNbqpA1kYkwNcb+MYxkxCLAPJGQzlv9J0C24h8V65Z4c5GLd/JEALDvpFCQgpLJqc0zw==} peerDependencies: bare-abort-controller: '*' peerDependenciesMeta: @@ -5947,15 +6138,15 @@ packages: bare-buffer: optional: true - bare-os@3.9.0: - resolution: {integrity: sha512-JTjuZyNIDpw+GytMO4a6TK1VXdVKKJr6DRxEHasyuYyShV2deuiHJK/ahGZlebc+SG0/wJCB9XK8gprBGDFi/Q==} + bare-os@3.9.1: + resolution: {integrity: sha512-6M5XjcnsygQNPMCMPXSK379xrJFiZ/AEMNBmFEmQW8d/789VQATvriyi5r0HYTL9TkQ26rn3kgdTG3aisbrXkQ==} engines: {bare: '>=1.14.0'} bare-path@3.0.0: resolution: {integrity: sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==} - bare-stream@2.13.0: - resolution: {integrity: sha512-3zAJRZMDFGjdn+RVnNpF9kuELw+0Fl3lpndM4NcEOhb9zwtSo/deETfuIwMSE5BXanA0FrN1qVjffGwAg2Y7EA==} + bare-stream@2.13.1: + resolution: {integrity: sha512-Vp0cnjYyrEC4whYTymQ+YZi6pBpfiICZO3cfRG8sy67ZNWe951urv1x4eW1BKNngw3U+3fPYb5JQvHbCtxH7Ow==} peerDependencies: bare-abort-controller: '*' bare-buffer: '*' @@ -5968,8 +6159,8 @@ packages: bare-events: optional: true - bare-url@2.4.2: - resolution: {integrity: sha512-/9a2j4ac6ckpmAHvod/ob7x439OAHst/drc2Clnq+reRYd/ovddwcF4LfoxHyNk5AuGBnPg+HqFjmE/Zpq6v0A==} + bare-url@2.4.3: + resolution: {integrity: sha512-Kccpc7ACfXaxfeInfqKcZtW4pT5YBn1mesc4sCsun6sRwtbJ4h+sNOaksUpYEJUKfN65YWC6Bw2OJEFiKxq8nQ==} base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} @@ -5978,8 +6169,8 @@ packages: resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==} engines: {node: ^4.5.0 || >= 5.9} - baseline-browser-mapping@2.10.20: - resolution: {integrity: sha512-1AaXxEPfXT+GvTBJFuy4yXVHWJBXa4OdbIebGN/wX5DlsIkU0+wzGnd2lOzokSk51d5LUmqjgBLRLlypLUqInQ==} + baseline-browser-mapping@2.10.31: + resolution: {integrity: sha512-MujYO3eP72uvmSE0i4wltsodRfIpZATP3jvzRNRGGxgzId7aVocVJJV3nf01qnzzKFGxQVC9bpWxl5cjxTr/7Q==} engines: {node: '>=6.0.0'} hasBin: true @@ -6007,8 +6198,8 @@ packages: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} - bits-ui@2.18.0: - resolution: {integrity: sha512-GLOBZRVy3hxNHIQ2MpD/+5aK9KcBFZRhUJtZ1UDABXdlVR4K6zFpgt4T+Rwuhf2sQzlc6yK1q/DprHPjwT4Pjw==} + bits-ui@2.18.1: + resolution: {integrity: sha512-KkemzKFH4T3gt3H+P86JcnAWExjByv/6vlwjm/BoCwTPHu03yiCdxbghdJLvFReQTe0acCAiRcKfmixxD6XvlA==} engines: {node: '>=20'} peerDependencies: '@internationalized/date': ^3.8.1 @@ -6017,8 +6208,8 @@ packages: bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} - body-parser@1.20.4: - resolution: {integrity: sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==} + body-parser@1.20.5: + resolution: {integrity: sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} body-parser@2.2.2: @@ -6039,14 +6230,14 @@ packages: resolution: {integrity: sha512-2hCgjEmP8YLWQ130n2FerGv7rYpfBmnmp9Uy2Le1vge6X3gZIfSmEzP5QTDElFxcvVcXlEn8Aq6MU/PZygIOog==} engines: {node: '>=14.16'} - brace-expansion@1.1.12: - resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + brace-expansion@1.1.14: + resolution: {integrity: sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==} brace-expansion@2.1.0: resolution: {integrity: sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==} - brace-expansion@5.0.5: - resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==} + brace-expansion@5.0.6: + resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==} engines: {node: 18 || 20 || >=22} braces@3.0.3: @@ -6078,12 +6269,13 @@ packages: resolution: {integrity: sha512-lHblz4ahamxpTmnsk+MNTRWsjYKv965MwOrSJyeD588rR3Jcu7swE+0wN5F+PbL5cjgu/9ObkhfzEPuofEMwLA==} engines: {node: '>=10.0.0'} - builtin-modules@5.0.0: - resolution: {integrity: sha512-bkXY9WsVpY7CvMhKSR6pZilZu9Ln5WDrKVBUXf2S443etkmEO4V58heTecXcUIsNsi4Rx8JUO4NfX1IcQl4deg==} + builtin-modules@5.2.0: + resolution: {integrity: sha512-02yxLeyxF4dNl6SlY6/5HfRSrSdZ/sCPoxy2kZNP5dZZX8LSAD9aE2gtJIUgWrsQTiMPl3mxESyrobSwvRGisQ==} engines: {node: '>=18.20'} - bullmq@5.76.1: - resolution: {integrity: sha512-9Xc5Pj4Ho0clodowuuUSydMOR4gCn+YxYYVQXbGJycO8r4jyxsff1rZl3CKj3k50c/B42gDDNTLJH6uwb3dYmg==} + bullmq@5.76.10: + resolution: {integrity: sha512-LWve7SpQjYSpCP2GEsWmoyzTz2H37L8HRmSTu3YihYsTOr5kJxrfEX6aEV7m6eskEMWXSHZYTMZepX6qNaH6CQ==} + engines: {node: '>=12.22.0'} bundle-name@4.1.0: resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} @@ -6114,14 +6306,14 @@ packages: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} + bytestreamjs@2.0.1: + resolution: {integrity: sha512-U1Z/ob71V/bXfVABvNr/Kumf5VyeQRBEm6Txb0PQ6S7V5GpBM3w4Cbqz/xPDicR5tN0uvDifng8C+5qECeGwyQ==} + engines: {node: '>=6.0.0'} + cac@6.7.14: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} - cacache@20.0.3: - resolution: {integrity: sha512-3pUp4e8hv07k1QlijZu6Kn7c9+ZpWWk4j3F8N3xPuCExULobqJydKYOTj1FTq58srkJsXvO7LbGAH4C0ZU3WGw==} - engines: {node: ^20.17.0 || >=22.9.0} - cacheable-lookup@7.0.0: resolution: {integrity: sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==} engines: {node: '>=14.16'} @@ -6134,8 +6326,8 @@ packages: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} - call-bind@1.0.8: - resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + call-bind@1.0.9: + resolution: {integrity: sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==} engines: {node: '>= 0.4'} call-bound@1.0.4: @@ -6168,12 +6360,12 @@ packages: caniuse-api@3.0.0: resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==} - caniuse-lite@1.0.30001790: - resolution: {integrity: sha512-bOoxfJPyYo+ds6W0YfptaCWbFnJYjh2Y1Eow5lRv+vI2u8ganPZqNm1JwNh0t2ELQCqIWg4B3dWEusgAmsoyOw==} + caniuse-lite@1.0.30001793: + resolution: {integrity: sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==} - canvas@2.11.2: - resolution: {integrity: sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==} - engines: {node: '>=6'} + canvas@3.2.3: + resolution: {integrity: sha512-PzE5nJZPz72YUAfo8oTp0u3fqqY7IzlTubneAihqDYAUcBk7ryeCmBbdJBEdaH0bptSOe2VT2Zwcb3UaFyaSWw==} + engines: {node: ^18.12.0 || >= 20.9.0} ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} @@ -6232,14 +6424,6 @@ packages: resolution: {integrity: sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==} engines: {node: '>= 6'} - chevrotain-allstar@0.3.1: - resolution: {integrity: sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==} - peerDependencies: - chevrotain: ^11.0.0 - - chevrotain@11.0.3: - resolution: {integrity: sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==} - chokidar@3.6.0: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} @@ -6277,9 +6461,6 @@ packages: cjs-module-lexer@2.2.0: resolution: {integrity: sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==} - class-transformer@0.5.1: - resolution: {integrity: sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==} - clean-css@5.3.3: resolution: {integrity: sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==} engines: {node: '>= 10.0'} @@ -6435,6 +6616,10 @@ packages: resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} engines: {node: '>= 12'} + commander@9.5.0: + resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==} + engines: {node: ^12.20.0 || >=14} + comment-json@5.0.0: resolution: {integrity: sha512-uiqLcOiVDJtBP8WGkZHEP+FZIhTzP1dxvn59EfoYUi9gqupjrBWVQkO2atDrbnKPwLeotFYDsuNb26uBMqB+hw==} engines: {node: '>= 6'} @@ -6468,9 +6653,6 @@ packages: resolution: {integrity: sha512-Uy5YN9KEu0WWDaZAVJ5FAmZoaJt9rdK6kH+utItPyGsCqCgaTKkrmZx3zoE0/3q6S3bcp3Ihkk+ZqPxWxFK5og==} engines: {node: '>=20'} - confbox@0.1.8: - resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} - config-chain@1.1.13: resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} @@ -6501,10 +6683,18 @@ packages: resolution: {integrity: sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==} engines: {node: '>=18'} + content-disposition@1.1.0: + resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==} + engines: {node: '>=18'} + content-type@1.0.5: resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} engines: {node: '>= 0.6'} + content-type@2.0.0: + resolution: {integrity: sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==} + engines: {node: '>=18'} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -6554,8 +6744,8 @@ packages: core-js-compat@3.49.0: resolution: {integrity: sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==} - core-js@3.47.0: - resolution: {integrity: sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg==} + core-js@3.49.0: + resolution: {integrity: sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg==} core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} @@ -6617,8 +6807,8 @@ packages: peerDependencies: postcss: ^8.4 - css-declaration-sorter@7.3.0: - resolution: {integrity: sha512-LQF6N/3vkAMYF4xoHLJfG718HRJh34Z8BnNhd6bosOMIVjMlhuZK5++oZa3uYAgrI5+7x2o27gUqTR2U/KjUOQ==} + css-declaration-sorter@7.4.0: + resolution: {integrity: sha512-LTuzjPoyA2vMGKKcaOqKSp7Ub2eGrNfKiZH4LpezxpNrsICGCSFvsQOI29psISxNZtaXibkC2CXzrQ5enMeGGw==} engines: {node: ^14 || ^16 || >=18} peerDependencies: postcss: ^8.0.9 @@ -6696,8 +6886,8 @@ packages: css.escape@1.5.1: resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} - cssdb@8.5.2: - resolution: {integrity: sha512-Pmoj9RmD8RIoIzA2EQWO4D4RMeDts0tgAH0VXdlNdxjuBGI3a9wMOIcUwaPNmD4r2qtIa06gqkIf7sECl+cBCg==} + cssdb@8.9.0: + resolution: {integrity: sha512-J8jOU/hLjaXcO1LldOLraJSQpfLXRKof0I7mtbRyOy2AAXgqst0x9rlgi2qXeD6d0ou3ZLqcPAMqYVbpCbrxEw==} cssesc@3.0.0: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} @@ -6739,6 +6929,9 @@ packages: csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + custom-media-element@1.4.6: + resolution: {integrity: sha512-/HRYqJOa1ob5ik4q7FIJVYxTJCFs/FL3+cQPAJjUf2uiqrDEzbTgB315gQ2rG8oK3w094W9m5tcB8S5Qah+caA==} + cytoscape-cose-bilkent@4.1.0: resolution: {integrity: sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==} peerDependencies: @@ -6749,8 +6942,8 @@ packages: peerDependencies: cytoscape: ^3.2.0 - cytoscape@3.33.1: - resolution: {integrity: sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==} + cytoscape@3.33.4: + resolution: {integrity: sha512-HIN5Pmd9MrX9BkV7tDwnOcEJCSFvCpc8X97h3f508J6I5FsqAY65wKOCvgH2CuP42CaahWaz4tuh32SOOIH7ww==} engines: {node: '>=0.10'} d3-array@2.12.1: @@ -6809,8 +7002,8 @@ packages: resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==} engines: {node: '>=12'} - d3-format@3.1.0: - resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==} + d3-format@3.1.2: + resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==} engines: {node: '>=12'} d3-geo@3.1.1: @@ -6896,15 +7089,15 @@ packages: resolution: {integrity: sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==} engines: {node: '>=0.12'} - dagre-d3-es@7.0.13: - resolution: {integrity: sha512-efEhnxpSuwpYOKRm/L5KbqoZmNNukHa/Flty4Wp62JRvgH2ojwVgPgdYyr4twpieZnyRDdIH7PY2mopX26+j2Q==} + dagre-d3-es@7.0.14: + resolution: {integrity: sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg==} data-urls@5.0.0: resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} engines: {node: '>=18'} - dayjs@1.11.19: - resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==} + dayjs@1.11.20: + resolution: {integrity: sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==} debounce-fn@6.0.0: resolution: {integrity: sha512-rBMW+F2TXryBwB54Q0d8drNEI+TfoS9JpNTAoVpukbWEhjXQq4rySFYLaqXMFXwdv61Zb2OHtj5bviSoimqxRQ==} @@ -6950,12 +7143,8 @@ packages: decimal.js@10.6.0: resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} - decode-named-character-reference@1.2.0: - resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==} - - decompress-response@4.2.1: - resolution: {integrity: sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==} - engines: {node: '>=8'} + decode-named-character-reference@1.3.0: + resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} decompress-response@6.0.0: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} @@ -6983,8 +7172,8 @@ packages: resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==} engines: {node: '>=18'} - default-browser@5.4.0: - resolution: {integrity: sha512-XDuvSq38Hr1MdN47EDvYtx3U0MTqpCEn+F6ft8z2vYDzMrvQhVp0ui9oQdqW3MvK3vqUETglt1tVGgjLuJ5izg==} + default-browser@5.5.0: + resolution: {integrity: sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==} engines: {node: '>=18'} defaults@1.0.4: @@ -7010,8 +7199,8 @@ packages: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} - delaunator@5.0.1: - resolution: {integrity: sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==} + delaunator@5.1.0: + resolution: {integrity: sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==} delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} @@ -7055,8 +7244,8 @@ packages: engines: {node: '>= 4.0.0'} hasBin: true - devalue@5.7.1: - resolution: {integrity: sha512-MUbZ586EgQqdRnC4yDrlod3BEdyvE4TapGYHMW2CiaW+KkkFmWEFqBUaLltEZCGi0iFXCEjRF0OjF0DV2QHjOA==} + devalue@5.8.1: + resolution: {integrity: sha512-4CXDYRBGqN+57wVJkuXBYmpAVUSg3L6JAQa/DFqm238G73E1wuyc/JhGQJzN7vUf/CMphYau2zXbfWzDR5aTEw==} devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} @@ -7103,9 +7292,9 @@ packages: resolution: {integrity: sha512-XJgGhoR/CLpqshm4d3L7rzH6t8NgDFUIIpztYlLHIApeJjMZKYJMz2zxPsYxnejq5h3ELYSw/RBsi3t5h7gNTA==} engines: {node: '>= 8.0'} - dockerode@4.0.10: - resolution: {integrity: sha512-8L/P9JynLBiG7/coiA4FlQXegHltRqS0a+KqI44P1zgQh8QLHTg7FKOwhkBgSJwZTeHsq30WRoVFLuwkfK0YFg==} - engines: {node: '>= 8.0'} + dockerode@5.0.0: + resolution: {integrity: sha512-C52mvJ+7lcyhWNfrzVfFsbTrBfy/ezE9FGEYLpu17FUeBcCkxERk9nN7uDl/478ynDiQ4U+5DbQC2vENHkVEtQ==} + engines: {node: '>= 14.17'} docusaurus-lunr-search@3.6.0: resolution: {integrity: sha512-CCEAnj5e67sUZmIb2hOl4xb4nDN07fb0fvRDDmdWlYpUvyS1CSKbw4lsGInLyUFEEEBzxQmT6zaVQdF/8Zretg==} @@ -7144,8 +7333,8 @@ packages: resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} engines: {node: '>= 4'} - dompurify@3.3.1: - resolution: {integrity: sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==} + dompurify@3.4.5: + resolution: {integrity: sha512-OrwIBKsdNSVEeubdJ1HBv/wNENRM9ytAVCv7YXt//A3vPdVMNuACRqK9mXCGCBW2ln7BT/A4X0jXHo2Gu89miA==} domutils@2.8.0: resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} @@ -7187,8 +7376,8 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - electron-to-chromium@1.5.343: - resolution: {integrity: sha512-YHnQ3MXI08icvL9ZKnEBy05F2EQ8ob01UaMOuMbM8l+4UcAq6MPPbBTJBbsBUg3H8JeZNt+O4fjsoWth3p6IFg==} + electron-to-chromium@1.5.360: + resolution: {integrity: sha512-GkcBt6YYAw9SxFWn+xVar4cLVGlXVuswwtRLBozi2zp0GjXs4ZnOrqV4zbXzg35n7w81hCkyJNYicgXlVHAmBA==} emoji-regex@10.6.0: resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} @@ -7213,25 +7402,22 @@ packages: resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} engines: {node: '>= 0.8'} - encoding@0.1.13: - resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} - end-of-stream@1.4.5: resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} - engine.io-client@6.6.4: - resolution: {integrity: sha512-+kjUJnZGwzewFDw951CDWcwj35vMNf2fcj7xQWOctq1F2i1jkDdVvdFG9kM/BEChymCH36KgjnW0NsL58JYRxw==} + engine.io-client@6.6.5: + resolution: {integrity: sha512-QCwxUDULPlXv8F6tqMMKx5dNkTe6OaBYRMPYeXKBlyOoKvAmE0ac6pW7fFhSscJ/5SI7666/U/B+MElbsrJlIg==} engine.io-parser@5.2.3: resolution: {integrity: sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==} engines: {node: '>=10.0.0'} - engine.io@6.6.5: - resolution: {integrity: sha512-2RZdgEbXmp5+dVbRm0P7HQUImZpICccJy7rN7Tv+SFa55pH+lxnuw6/K1ZxxBfHoYpSkHLAO92oa8O4SwFXA2A==} + engine.io@6.6.8: + resolution: {integrity: sha512-2agL3ueZhqxoVrfmntO8yuVj+uNSlIOnhykYHk3Cq0ShYPdUjjUiSJrQvXjq01I9jAuI0Zl2YO8Evv5Mqytm5g==} engines: {node: '>=10.2.0'} - enhanced-resolve@5.21.0: - resolution: {integrity: sha512-otxSQPw4lkOZWkHpB3zaEQs6gWYEsmX4xQF68ElXC/TWvGxGMSGOvoNbaLXm6/cS/fSfHtsEdw90y20PCd+sCA==} + enhanced-resolve@5.21.5: + resolution: {integrity: sha512-mLCNbrQli11K1ySUmuNt4ZUB3OpGIDq4q2vTBTf5cL2lpsRjI9QKqSD0ndjW8FyvcW/Jj46gMe9syyHAsvMa/A==} engines: {node: '>=10.13.0'} entities@2.2.0: @@ -7257,9 +7443,6 @@ packages: resolution: {integrity: sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - err-code@2.0.3: - resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} - error-ex@1.3.4: resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} @@ -7285,6 +7468,9 @@ packages: resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} engines: {node: '>= 0.4'} + es-toolkit@1.46.1: + resolution: {integrity: sha512-5eNtXOs3tbfxXOj04tjjseeWkRWaoCjdEI+96DgwzZoe6c9juL49pXlzAFTI72aWC9Y8p7168g6XIKjh7k6pyQ==} + es5-ext@0.10.64: resolution: {integrity: sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==} engines: {node: '>=0.10'} @@ -7315,6 +7501,11 @@ packages: engines: {node: '>=18'} hasBin: true + esbuild@0.27.7: + resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} + engines: {node: '>=18'} + hasBin: true + esbuild@0.28.0: resolution: {integrity: sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==} engines: {node: '>=18'} @@ -7362,9 +7553,9 @@ packages: oxlint: optional: true - eslint-plugin-compat@7.0.1: - resolution: {integrity: sha512-wDID2fVIAfxV9R1uSkCn5HscnNu8yMxDF1IaQGyD1C6XuWwJbuaDgMOSkVgOom0LzY8z0fXXXCy7AQQTERQUvQ==} - engines: {node: '>=18.x'} + eslint-plugin-compat@7.0.2: + resolution: {integrity: sha512-gN8hF+4NzMsHUbr4m/TYZK0FtW3DcV4g8rXpTsY2EV5xiRD8jsilUlB9lNSkGGX0veDCxMhKSWbSd+faJByQDA==} + engines: {node: '>=22.x'} peerDependencies: eslint: ^9.0.0 || ^10.0.0 @@ -7422,8 +7613,8 @@ packages: resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - eslint@10.2.1: - resolution: {integrity: sha512-wiyGaKsDgqXvF40P8mDwiUp/KQjE1FdrIEJsM8PZ3XCiniTMXS3OHWWUe5FI5agoCnr8x4xPrTDZuxsBlNHl+Q==} + eslint@10.4.0: + resolution: {integrity: sha512-loXy6bWOoP3EP6JA7jo6p5jMpBJmHmsNZM5SFRHLdh1MGOPurMnNBj4ZlAbaqUAaQWbCr7jHV4P7gzAyryZWkQ==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} hasBin: true peerDependencies: @@ -7456,8 +7647,13 @@ packages: resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} engines: {node: '>=0.10'} - esrap@2.2.4: - resolution: {integrity: sha512-suICpxAmZ9A8bzJjEl/+rLJiDKC0X4gYWUxT6URAWBLvlXmtbZd5ySMu/N2ZGEtMCAmflUDPSehrP9BQcsGcSg==} + esrap@2.2.9: + resolution: {integrity: sha512-4KijP+NxCWthMCUC3qHbE6n4vCjqgJS1uAYKhuT/GWfFTf1Qyive2TgOjep+gzbSzRfnNyaN/UU9YmdOt8Eg0A==} + peerDependencies: + '@typescript-eslint/types': ^8.2.0 + peerDependenciesMeta: + '@typescript-eslint/types': + optional: true esrecurse@4.3.0: resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} @@ -7506,8 +7702,8 @@ packages: resolution: {integrity: sha512-UVQ72Rqjy/ZKQalzV5dCCJP80GrmPrMxh6NlNf+erV6ObL0ZFkhCstWRawS85z3smdr3d2wXPsZEY7rDPfGd2g==} engines: {node: '>=6.0.0'} - eta@4.5.1: - resolution: {integrity: sha512-EaNCGm+8XEIU7YNcc+THptWAO5NfKBHHARxt+wxZljj9bTr/+arRoOm9/MpGt4n6xn9fLnPFRSoLD0WFYGFUxQ==} + eta@4.6.0: + resolution: {integrity: sha512-lW6is4T1NFOYnmqGZIfvixqj7A7sSvScF+DN8EK6K58xI5MZ5UvYe0GjopxOXQtZvUn4eDdVuZ8XSoYWTMEKwA==} engines: {node: '>=20'} etag@1.8.1: @@ -7535,27 +7731,27 @@ packages: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} - eventsource-parser@3.0.6: - resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==} - engines: {node: '>=18.0.0'} - execa@5.1.1: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} - exiftool-vendored.exe@13.57.0: - resolution: {integrity: sha512-9ENCWzUiFy6F/O4jSX50ygSGrTOtvoqJFWE0zAOl7VL/EFooLWNF0LkaNSox0ibbIsQz5rWSKi0TPlEbF4qBIw==} + exiftool-vendored.exe@13.58.0: + resolution: {integrity: sha512-pV7SjQeOu4Q77DWuyF+hlRYWVlRcSAqfqTTujBZeGUy/Q9+RPAy877YgSZIxKOYW1TxmmL8KyBGxaG0JKYG8BQ==} os: [win32] - exiftool-vendored.pl@13.57.0: - resolution: {integrity: sha512-7HYhrIygbfKD+E/sUF9L8YEs7qCEFLFWKoeevJllnD9jxVvZ09tfFsjbBPQ7SAgGwWSHW//SVULFHLgrO8JsBw==} + exiftool-vendored.pl@13.58.0: + resolution: {integrity: sha512-+Z2xhZrYLMu/anO/s14AaS/K5HMJ5Cw9C3KefIeYNpkZRN4RRBJHm7R34yjj9Pv+elqYRZrQV9NcqvkBLn/68w==} os: ['!win32'] hasBin: true - exiftool-vendored@35.18.0: - resolution: {integrity: sha512-QBtYNz71VAwZWqxFP1iWWS9qwOx3b9MSpk0GAMyIfS8gupUWsOyhn4i2WrB4OlRSQPuQ2YeKSw2fygi6E0LGiw==} + exiftool-vendored@35.20.0: + resolution: {integrity: sha512-Yn66dSBaWGcUaSbm5Nl4G28rxtceLlWf4PstqJMbLix9sN7w0okWHPEvdudiP56Q5Cjl7v3TLyKKwowUFlbD8g==} engines: {node: '>=20.0.0'} + expand-template@2.0.3: + resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} + engines: {node: '>=6'} + expect-type@1.3.0: resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} engines: {node: '>=12.0.0'} @@ -7563,8 +7759,8 @@ packages: exponential-backoff@3.1.3: resolution: {integrity: sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==} - express@4.22.1: - resolution: {integrity: sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==} + express@4.22.2: + resolution: {integrity: sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==} engines: {node: '>= 0.10.0'} express@5.2.1: @@ -7581,8 +7777,8 @@ packages: extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} - fabric@7.3.1: - resolution: {integrity: sha512-RoLAQzUX+/3RNMYKliuN0P2HXdSDEGzyjS7FnmEbo3nhb8LFh59T+l3f6ApIu5LT4YB49YfMNrEajeIbutmD7Q==} + fabric@7.4.0: + resolution: {integrity: sha512-NalYDc3eifTl1C33zryQwpH6+XA/2ClxQrH9vkASkZw3tbkRmorpikhYMmxhUTmi7O3e9ODz0vOT8qfaCh9IVA==} engines: {node: '>=20.0.0'} factory.ts@1.4.2: @@ -7611,8 +7807,8 @@ packages: fast-safe-stringify@2.1.1: resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} - fast-uri@3.1.0: - resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + fast-uri@3.1.2: + resolution: {integrity: sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==} fastq@1.20.1: resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} @@ -7637,8 +7833,8 @@ packages: resolution: {integrity: sha512-u5/sxGfiMfZNtJ3OvQpXcvotFpYkL0n9u9mM2vkui2nGo8b4wvDkJ8gAkYqbA8QpGyFCv3RK0Z+Iv+9veCS9bQ==} engines: {node: '>=0.4.0'} - fflate@0.8.2: - resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} + fflate@0.8.3: + resolution: {integrity: sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==} figures@3.2.0: resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} @@ -7709,8 +7905,8 @@ packages: engines: {node: '>=18'} deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. - follow-redirects@1.15.11: - resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} + follow-redirects@1.16.0: + resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==} engines: {node: '>=4.0'} peerDependencies: debug: '*' @@ -7763,9 +7959,6 @@ packages: resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} engines: {node: '>= 0.8'} - front-matter@4.0.2: - resolution: {integrity: sha512-I8ZuJ/qG92NWX8i5x1Y8qyj3vizhXS31OxjKDu3LKP+7/qBgfIKValiZIEwoVoJKUHlhWtYrktkxV1XsX+pPlg==} - fs-constants@1.0.0: resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} @@ -7773,18 +7966,14 @@ packages: resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} engines: {node: '>=12'} - fs-extra@11.3.4: - resolution: {integrity: sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==} + fs-extra@11.3.5: + resolution: {integrity: sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==} engines: {node: '>=14.14'} fs-minipass@2.1.0: resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} engines: {node: '>= 8'} - fs-minipass@3.0.3: - resolution: {integrity: sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - fs-monkey@1.1.0: resolution: {integrity: sha512-QMUezzXWII9EV5aTFXW1UBVUO77wYPpjqIF8/AviUCThNeSYZykpoTixUeaNNBwmCev0AMDWMAni+f8Hxb1IFw==} @@ -7809,6 +7998,10 @@ packages: engines: {node: '>=10'} deprecated: This package is no longer supported. + generic-pool@3.9.0: + resolution: {integrity: sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==} + engines: {node: '>= 4'} + gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} @@ -7816,8 +8009,8 @@ packages: geo-coordinates-parser@1.7.4: resolution: {integrity: sha512-gVGxBW+s1csexXVMf5bIwz3TH9n4sCEglOOOqmrPk8YazUI5f79jCowKjTw05m/0h1//3+Z2m/nv8IIozgZyUw==} - geo-tz@8.1.6: - resolution: {integrity: sha512-6YEper1rtHi1l3ZS99oy9ZBvrBO4qsLzvQwZoxYtV3fyxPuxh7yqiOUWRVs1bzSj693EXEO4k60jKXQmkY/JnQ==} + geo-tz@8.1.7: + resolution: {integrity: sha512-skkwQunTD1hpixwESUv8GeLLl8DElpUmnZfp9trQVgVGVIRTeQJHmCcWELCr8lsiVKdu661TWzR9hsdlGCEEKA==} engines: {node: '>=16'} geobuf@3.0.2: @@ -7832,8 +8025,8 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} - get-east-asian-width@1.5.0: - resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==} + get-east-asian-width@1.6.0: + resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==} engines: {node: '>=18'} get-intrinsic@1.3.0: @@ -7855,8 +8048,11 @@ packages: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} - get-tsconfig@4.13.0: - resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==} + get-tsconfig@4.14.0: + resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==} + + github-from-package@0.0.0: + resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} github-slugger@1.5.0: resolution: {integrity: sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw==} @@ -7910,8 +8106,8 @@ packages: resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==} engines: {node: '>=18'} - globals@17.5.0: - resolution: {integrity: sha512-qoV+HK2yFl/366t2/Cb3+xxPUo5BuMynomoDmiaZBIdbs+0pYbjfZU+twLhGKp4uCZ/+NbtpVepH5bGCxRyy2g==} + globals@17.6.0: + resolution: {integrity: sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA==} engines: {node: '>=18'} globalyzer@0.1.0: @@ -7990,8 +8186,8 @@ packages: resolution: {integrity: sha512-IrsVwUHhEULx3R8f/aA8AHuEzAorplsab/v8HBzEiIukwq5i/EC+xmOW+HfP1OaDP+2JkgT1yILHN2O3UFIbcA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - hasown@2.0.2: - resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + hasown@2.0.3: + resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==} engines: {node: '>= 0.4'} hast-util-from-parse5@6.0.1: @@ -8060,6 +8256,12 @@ packages: history@4.10.1: resolution: {integrity: sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==} + hls-video-element@1.5.11: + resolution: {integrity: sha512-tJJ65/52CDxj8XFyIve6zT9nVVdUIc6mqvKR25X0ycPKHk07rpjp4xxVteeCefDUBSf/tFLhlICFmn3KWj37xA==} + + hls.js@1.6.16: + resolution: {integrity: sha512-VSIRpLfRwlAAdGL4wiTucx2ScRipo0ed1FBatWkyt832jC4CReKstga6yIhYVwGu9LOBjuX9wzmRMeQdBJtzEA==} + hogan.js@3.0.2: resolution: {integrity: sha512-RqGs4wavGYJWE07t35JQccByczmNUXQT0E12ZYV1VKYu5UiAU9lsos/yBAcf840+zrUQQxgVduCR5/B8nNtibg==} hasBin: true @@ -8098,8 +8300,8 @@ packages: html-void-elements@3.0.0: resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} - html-webpack-plugin@5.6.5: - resolution: {integrity: sha512-4xynFbKNNk+WlzXeQQ+6YYsH2g7mpfPszQZUi3ovKlj+pDmngQ7vRXjrrmGROabmKwyQkcgcX5hqfOwHbFmK5g==} + html-webpack-plugin@5.6.7: + resolution: {integrity: sha512-md+vXtdCAe60s1k6AU3dUyMJnDxUyQAwfwPKoLisvgUF1IXjtlLsk2se54+qfL9Mdm26bbwvjJybpNx48NKRLw==} engines: {node: '>=10.13.0'} peerDependencies: '@rspack/core': 0.x || 1.x @@ -8126,10 +8328,6 @@ packages: http-deceiver@1.2.7: resolution: {integrity: sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==} - http-errors@1.6.3: - resolution: {integrity: sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==} - engines: {node: '>= 0.6'} - http-errors@1.8.1: resolution: {integrity: sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==} engines: {node: '>= 0.6'} @@ -8233,14 +8431,17 @@ packages: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} - import-in-the-middle@3.0.0: - resolution: {integrity: sha512-OnGy+eYT7wVejH2XWgLRgbmzujhhVIATQH0ztIeRilwHBjTeG3pD+XnH3PKX0r9gJ0BuJmJ68q/oh9qgXnNDQg==} + import-in-the-middle@3.0.1: + resolution: {integrity: sha512-pYkiyXVL2Mf3pozdlDGV6NAObxQx13Ae8knZk1UJRJ6uRW/ZRmTGHlQYtrsSl7ubuE5F8CD1z+s1n4RHNuTtuA==} engines: {node: '>=18'} import-lazy@4.0.0: resolution: {integrity: sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==} engines: {node: '>=8'} + import-meta-resolve@4.2.0: + resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} + imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} @@ -8261,9 +8462,6 @@ packages: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. - inherits@2.0.3: - resolution: {integrity: sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==} - inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} @@ -8291,8 +8489,8 @@ packages: intl-messageformat@10.7.18: resolution: {integrity: sha512-m3Ofv/X/tV8Y3tHXLohcuVuhWKo7BBq62cqY15etqmLxg2DZ34AGGgQDeR+SCta2+zICb1NX83af0GJmbQ1++g==} - intl-messageformat@11.2.1: - resolution: {integrity: sha512-1gAVEUt3wEPvTqML4Fsw9klZV5j0vszQxayP/fi6gUroAc8AUHiNaisBKLWxybL1AdWq1mP07YV1q8v4N92ilQ==} + intl-messageformat@11.2.6: + resolution: {integrity: sha512-afAN2yNN7zjB77G1ZC5L8GtLrEshyBvOQXz88flxCO/ocTIQist98gu0r/O6H/SSiQhQsOOtWPxmCEvtDABXXQ==} invariant@2.2.4: resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} @@ -8301,16 +8499,12 @@ packages: resolution: {integrity: sha512-HuEDBTI70aYdx1v6U97SbNx9F1+svQKBDo30o0b9fw055LMepzpOOd0Ccg9Q6tbqmBSJaMuY0fB7yw9/vjBYCA==} engines: {node: '>=12.22.0'} - ip-address@10.1.0: - resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==} - engines: {node: '>= 12'} - ipaddr.js@1.9.1: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} - ipaddr.js@2.3.0: - resolution: {integrity: sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==} + ipaddr.js@2.4.0: + resolution: {integrity: sha512-9VGk3HGanVE6JoZXHiCpnGy5X0jYDnN4EA4lntFPj+1vIWlFhIylq2CrrCOJH9EAhc5CYhq18F2Av2tgoAPsYQ==} engines: {node: '>= 10'} is-alphabetical@2.0.1: @@ -8338,8 +8532,8 @@ packages: resolution: {integrity: sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==} hasBin: true - is-core-module@2.16.1: - resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + is-core-module@2.16.2: + resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==} engines: {node: '>= 0.4'} is-decimal@2.0.1: @@ -8395,8 +8589,8 @@ packages: resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==} engines: {node: '>=12'} - is-network-error@1.3.0: - resolution: {integrity: sha512-6oIwpsgRfnDiyEDLMay/GqCl3HoAtH5+RUKW29gYkL0QA+ipzpDLA16yQs7/RHCSu+BwgbJaOUqa4A99qNVQVw==} + is-network-error@1.3.2: + resolution: {integrity: sha512-PhBY86zaxNZUuWP6h13Vu5oFe0XY6/UlKzQnYFELzGVHygP3MxmvTfYSG7GN3aIab/iWudSMgjSnG9Dq+nHrgA==} engines: {node: '>=16'} is-npm@6.1.0: @@ -8510,10 +8704,6 @@ packages: resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} engines: {node: '>=10'} - istanbul-lib-source-maps@5.0.6: - resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==} - engines: {node: '>=10'} - istanbul-reports@3.2.0: resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} engines: {node: '>=8'} @@ -8548,8 +8738,8 @@ packages: resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} hasBin: true - jiti@2.6.1: - resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} + jiti@2.7.0: + resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} hasBin: true jmespath@0.16.0: @@ -8559,8 +8749,8 @@ packages: joi@17.13.3: resolution: {integrity: sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==} - jose@6.2.2: - resolution: {integrity: sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ==} + jose@6.2.3: + resolution: {integrity: sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==} js-tokens@10.0.0: resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} @@ -8583,7 +8773,7 @@ packages: resolution: {integrity: sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==} engines: {node: '>=18'} peerDependencies: - canvas: 2.11.2 + canvas: 3.2.3 peerDependenciesMeta: canvas: optional: true @@ -8612,9 +8802,6 @@ packages: json-schema-typed@8.0.2: resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} - json-schema@0.4.0: - resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} - json-source-map@0.6.1: resolution: {integrity: sha512-1QoztHPsMQqhDq0hlXY5ZqcEdUzxQEIxgFkKl4WUp2pgShObl+9ovi4kRh2TfvAfxAoHOJ9vIMEqk3k4iex7tg==} @@ -8660,12 +8847,12 @@ packages: jws@4.0.1: resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==} - katex@0.16.27: - resolution: {integrity: sha512-aeQoDkuRWSqQN6nSvVCEFvfXdqo1OQiCmmW1kc9xSdjutPv7BGO7pqY9sQRJpMOGrEdfDgF2TfRXe5eUAD2Waw==} + katex@0.16.47: + resolution: {integrity: sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==} hasBin: true - kdbush@4.0.2: - resolution: {integrity: sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA==} + kdbush@4.1.0: + resolution: {integrity: sha512-e9vurzrXJQrFX6ckpHP3bvj5l+9CnYzkxDNnNQ1h2QTqdWsUAJgXiKdGNcOa1EY85dU8KbQ+z/FdQdB7P+9yfQ==} keygrip@1.1.0: resolution: {integrity: sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==} @@ -8709,20 +8896,16 @@ packages: postgres: optional: true - kysely@0.28.16: - resolution: {integrity: sha512-3i5pmOiZvMDj00qhrIVbH0AnioVTx22DMP7Vn5At4yJO46iy+FM8Y/g61ltenLVSo3fiO8h8Q3QOFgf/gQ72ww==} + kysely@0.28.17: + resolution: {integrity: sha512-nbD8lB9EB3wNdMhOCdx5Li8DxnLbvKByylRLcJ1h+4SkrowVeECAyZlyiKMThF7xFdRz0jSQ2MoJr+wXux2y0Q==} engines: {node: '>=20.0.0'} - langium@3.3.1: - resolution: {integrity: sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w==} - engines: {node: '>=16.0.0'} - latest-version@7.0.0: resolution: {integrity: sha512-KvNT4XqAMzdcL6ka6Tl3i2lYeFDgXNCuIX+xNx6ZMVR1dFq+idXd9FLKNMOIx0t9mJ9/HudyX4oZWXZQ0UJHeg==} engines: {node: '>=14.16'} - launch-editor@2.12.0: - resolution: {integrity: sha512-giOHXoOtifjdHqUamwKq6c49GzBdLjvxrd2D+Q4V6uOHopJv7p9VJxikDsQ/CBXZbEITgUqSVHXLTG3VhPP1Dg==} + launch-editor@2.13.2: + resolution: {integrity: sha512-4VVDnbOpLXy/s8rdRCSXb+zfMeFR0WlJWpET1iA9CQdlZDfwyLjUuGQzXU4VeOoey6AicSAluWan7Etga6Kcmg==} layout-base@1.0.2: resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==} @@ -8838,8 +9021,8 @@ packages: resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - loader-runner@4.3.1: - resolution: {integrity: sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==} + loader-runner@4.3.2: + resolution: {integrity: sha512-DFEqQ3ihfS9blba08cLfYf1NRAIEm+dDjic073DRDc3/JspI/8wYmtDsHwd3+4hwvdxSK7PGaElfTmm0awWJ4w==} engines: {node: '>=6.11.5'} loader-utils@2.0.4: @@ -8861,9 +9044,6 @@ packages: resolution: {integrity: sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - lodash-es@4.17.21: - resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} - lodash-es@4.18.1: resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==} @@ -8944,8 +9124,8 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - lru-cache@11.3.5: - resolution: {integrity: sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==} + lru-cache@11.5.0: + resolution: {integrity: sha512-5YgH9UJd7wVb9hIouI2adWpgqrrICkt070Dnj8EUY1+B4B2P9eRLPAkAAo6NICA7CEhOIeBHl46u9zSNpNu7zA==} engines: {node: 20 || >=22} lru-cache@5.1.1: @@ -8954,8 +9134,8 @@ packages: lru-queue@0.1.0: resolution: {integrity: sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==} - lunr-languages@1.14.0: - resolution: {integrity: sha512-hWUAb2KqM3L7J5bcrngszzISY4BxrXn/Xhbb9TTCJYEGqlR1nG67/M14sp09+PTIRklobrn57IAxcdcO/ZFyNA==} + lunr-languages@1.20.0: + resolution: {integrity: sha512-3LVgE7ekWXt04NBci/hjm+NXJxXZeRXuyClL0kA0HONyBOjxhP3ZQkuWIM4Ok3pbeptUW/rj3XcJcJuJVPwPYA==} lunr@2.3.9: resolution: {integrity: sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==} @@ -8974,11 +9154,8 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - magicast@0.3.5: - resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} - - magicast@0.5.2: - resolution: {integrity: sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==} + magicast@0.5.3: + resolution: {integrity: sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw==} make-dir@3.1.0: resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} @@ -8988,10 +9165,6 @@ packages: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} engines: {node: '>=10'} - make-fetch-happen@15.0.3: - resolution: {integrity: sha512-iyyEpDty1mwW3dGlYXAJqC/azFn5PPvgKVwXayOGBSmKLxhKZ9fg4qIan2ePpp1vJIwfFiO34LAPZgq9SZW9Aw==} - engines: {node: ^20.17.0 || >=22.9.0} - maplibre-gl@5.24.0: resolution: {integrity: sha512-ALyFxgtd5R+65UqZ/++lOqwWcC0SNho9c27fYSyLmG7AfnAul2o46F05aDJGPbFU57wos9dgcIySHs0Xe6ia3A==} engines: {node: '>=16.14.0', npm: '>=8.1.0'} @@ -9016,11 +9189,6 @@ packages: engines: {node: '>= 20'} hasBin: true - marked@17.0.6: - resolution: {integrity: sha512-gB0gkNafnonOw0obSTEGZTT86IuhILt2Wfx0mWH/1Au83kybTayroZ/V6nS25mN7u8ASy+5fMhgB3XPNrOZdmA==} - engines: {node: '>= 20'} - hasBin: true - math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -9031,8 +9199,8 @@ packages: mdast-util-find-and-replace@3.0.2: resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} - mdast-util-from-markdown@2.0.2: - resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==} + mdast-util-from-markdown@2.0.3: + resolution: {integrity: sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==} mdast-util-frontmatter@2.0.1: resolution: {integrity: sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA==} @@ -9085,12 +9253,15 @@ packages: mdn-data@2.0.30: resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} - mdn-data@2.27.1: - resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} + mdn-data@2.28.0: + resolution: {integrity: sha512-uy9AS1yt+wW5eUEefgE3lOpqPghanUttycV0GXKbiXyBjwvbeE8XPj4u1C+voRfz7dEjwU4NDHTMfZ/s/JtZrQ==} media-chrome@4.19.0: resolution: {integrity: sha512-HWhDTwts+BSbdPkkB1VsJXp5kvL0IxY7xFT5tBwliM2+89kTPVTnHnev+9it2f9PweANjT/C8/C/S0PW9oyZbA==} + media-tracks@0.3.5: + resolution: {integrity: sha512-l54rkKXlLBt3ob3zOLWHcnjvwUmX5bNEZ70igyapOZZC9imzqBmq1oz8p2roiV04KhjblFIi2hetLPF1oYVLRA==} + media-typer@0.3.0: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} engines: {node: '>= 0.6'} @@ -9129,8 +9300,8 @@ packages: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} - mermaid@11.12.2: - resolution: {integrity: sha512-n34QPDPEKmaeCG4WDMGy0OT6PSyxKCfy2pJgShP+Qow2KLrvWjclwbc3yXfSIf4BanqWEhQEpngWwNp/XhZt6w==} + mermaid@11.15.0: + resolution: {integrity: sha512-pTMbcf3rWdtLiYGpmoTjHEpeY8seiy6sR+9nD7LOs8KfUbHE4lOUAprTRqRAcWSQ6MQpdX+YEsxShtGsINtPtw==} methods@1.1.2: resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} @@ -9305,10 +9476,6 @@ packages: resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} engines: {node: '>=18'} - mimic-response@2.1.0: - resolution: {integrity: sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==} - engines: {node: '>=8'} - mimic-response@3.1.0: resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} engines: {node: '>=10'} @@ -9321,8 +9488,8 @@ packages: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} - mini-css-extract-plugin@2.9.4: - resolution: {integrity: sha512-ZWYT7ln73Hptxqxk2DxPU9MmapXRhxkJD6tkSR04dnQxm8BGu2hzgKLugK5yySD97u/8yy7Ma7E76k9ZdvtjkQ==} + mini-css-extract-plugin@2.10.2: + resolution: {integrity: sha512-AOSS0IdEB95ayVkxn5oGzNQwqAi2J0Jb/kKm43t7H73s8+f5873g0yuj0PNvK4dO75mu5DHg4nlgp4k6Kga8eg==} engines: {node: '>= 12.13.0'} peerDependencies: webpack: ^5.0.0 @@ -9348,26 +9515,6 @@ packages: minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - minipass-collect@2.0.1: - resolution: {integrity: sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==} - engines: {node: '>=16 || 14 >=14.17'} - - minipass-fetch@5.0.1: - resolution: {integrity: sha512-yHK8pb0iCGat0lDrs/D6RZmCdaBT64tULXjdxjSMAqoDi18Q3qKEUTHypHQZQd9+FYpIS+lkvpq6C/R6SbUeRw==} - engines: {node: ^20.17.0 || >=22.9.0} - - minipass-flush@1.0.5: - resolution: {integrity: sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==} - engines: {node: '>= 8'} - - minipass-pipeline@1.2.4: - resolution: {integrity: sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==} - engines: {node: '>=8'} - - minipass-sized@2.0.0: - resolution: {integrity: sha512-zSsHhto5BcUVM2m1LurnXY6M//cGhVaegT71OfOXoprxT6o780GZd792ea6FfrQkuU4usHZIUczAQMRUE2plzA==} - engines: {node: '>=8'} - minipass@3.3.6: resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} engines: {node: '>=8'} @@ -9405,11 +9552,8 @@ packages: engines: {node: '>=10'} hasBin: true - mlly@1.8.0: - resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} - - mnemonist@0.40.3: - resolution: {integrity: sha512-Vjyr90sJ23CKKH/qPAgUKicw/v6pRoamxIEDFOF8uSgFME7DqPRpHgRTejWVjkdGg5dXj0/NyxZHZ9bcjH+2uQ==} + mnemonist@0.40.4: + resolution: {integrity: sha512-ZAv+KNavneRVzu4tUeOgzkScI3W5BGwZ3rkxIpKtzzVgfTtWQFN1CgX0U72cyvyh3iTuHL3SiSmrQxTlryEIcw==} mock-fs@5.5.0: resolution: {integrity: sha512-d/P1M/RacgM3dB0sJ8rjeRNXxtapkPCUnMGmIN0ixJ16F/E4GUZCvWcSGfWGz8eaXYvn1s9baUwNjI4LOPEjiA==} @@ -9439,8 +9583,8 @@ packages: resolution: {integrity: sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==} hasBin: true - msgpackr@1.11.5: - resolution: {integrity: sha512-UjkUHN0yqp9RWKy0Lplhh+wlpdt9oQBYgULZOiFhV3VclSF1JnSQWZ5r9gORQlNYaUKQoR8itv7g7z1xDDuACA==} + msgpackr@2.0.1: + resolution: {integrity: sha512-9J+tqTEsbHqY8YohazYgty7LgerFIWxvMLpUjqETSmjHojtJm2WnX2kK/2a1fLI7CO7ERP1YSEUXMucz4j+yBA==} multer@2.1.1: resolution: {integrity: sha512-mo+QTzKlx8R7E5ylSXxWzGoXoZbOsRMpyitcht8By2KHvMbf3tjwosZ/Mu/XYU6UuJ3VZnODIrak5ZrPiPyB6A==} @@ -9460,22 +9604,29 @@ packages: resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} engines: {node: ^18.17.0 || >=20.5.0} + mylas@2.1.14: + resolution: {integrity: sha512-BzQguy9W9NJgoVn2mRWzbFrFWWztGCcng2QI9+41frfk+Athwgx3qhqhvStz7ExeUUu7Kzw427sNzHpEZNINog==} + engines: {node: '>=16.0.0'} + mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} - nan@2.26.2: - resolution: {integrity: sha512-0tTvBTYkt3tdGw22nrAy50x7gpbGCCFH3AFcyS5WiUu7Eu4vWlri1woE6qHBSfy11vksDqkiwjOnlR7WV8G1Hw==} + nan@2.27.0: + resolution: {integrity: sha512-hC+0LidcL3XE4rp1C4H54KujgXKzbfyTngZTwBByQxsOxCEKZT0MPQ4hOKUH2jU1OYstqdDH4onyHPDzcV0XdQ==} - nanoid@3.3.11: - resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + nanoid@3.3.12: + resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - nanoid@5.1.9: - resolution: {integrity: sha512-ZUvP7KeBLe3OZ1ypw6dI/TzYJuvHP77IM4Ry73waSQTLn8/g8rpdjfyVAh7t1/+FjBtG4lCP42MEbDxOsRpBMw==} + nanoid@5.1.11: + resolution: {integrity: sha512-v+KEsUv2ps74PaSKv0gHTxTCgMXOIfBEbaqa6w6ISIGC7ZsvHN4N9oJ8d4cmf0n5oTzQz2SLmThbQWhjd/8eKg==} engines: {node: ^18 || >=20} hasBin: true + napi-build-utils@2.0.0: + resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} + natural-compare-lite@1.4.0: resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==} @@ -9525,15 +9676,15 @@ packages: kysely: 0.x reflect-metadata: ^0.1.13 || ^0.2.2 - nestjs-otel@7.0.1: - resolution: {integrity: sha512-NKce9aAJ263rcqaj3etHmv5KE+VALBqjGkPmZYvaesIb7AT7WBA3YXiEXmkJdKsnF2ZwmNFUJXCQWPn91Hrc8A==} - engines: {node: '>= 20'} + nestjs-otel@8.0.3: + resolution: {integrity: sha512-48W0ksSnvRZSO7GUY3R76TeHtPwThAhjPOeyH7EOzhLnsrPQ7Msk8nPrU5FpTiIzUapoubdx+jfolRlJC8L21g==} + engines: {node: '>= 22'} peerDependencies: '@nestjs/common': '>= 11 < 12' '@nestjs/core': '>= 11 < 12' - nestjs-zod@5.3.0: - resolution: {integrity: sha512-QY6imXm9heMOpWigjFHgMWPvc1ZQHeNQ7pdogo9Q5xj5F8HpqZ972vKlVdkaTyzYlOXJP/yVy3wlF1EjubDQPg==} + nestjs-zod@5.4.0: + resolution: {integrity: sha512-dxVpy1fjfK4kp+ztK+7xQP46fpvZxkeR/jcEdIvEGh/2o71iwXuy/hrKOWSPhJ1nQXV4iBdHqMizndn2GTaXDg==} peerDependencies: '@nestjs/common': ^10.0.0 || ^11.0.0 '@nestjs/swagger': ^7.4.2 || ^8.0.0 || ^11.0.0 @@ -9549,6 +9700,10 @@ packages: no-case@3.0.4: resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} + node-abi@3.92.0: + resolution: {integrity: sha512-KdHvFWZjEKDf0cakgFjebl371GPsISX2oZHcuyKqM7DtogIsHrqKeLTo8wBHxaXRAQlY2PsPlZmfo+9ZCxEREQ==} + engines: {node: '>=10'} + node-abort-controller@3.1.1: resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==} @@ -9558,8 +9713,8 @@ packages: node-addon-api@7.1.1: resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} - node-addon-api@8.5.0: - resolution: {integrity: sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==} + node-addon-api@8.7.0: + resolution: {integrity: sha512-9MdFxmkKaOYVTV+XVRG8ArDwwQ77XIgIPyKASB1k3JPq3M8fGQQQE3YpMOrKm6g//Ktx8ivZr8xo1Qmtqub+GA==} engines: {node: ^18 || ^20 || >= 21} node-emoji@1.11.0: @@ -9578,10 +9733,6 @@ packages: encoding: optional: true - node-forge@1.3.3: - resolution: {integrity: sha512-rLvcdSyRCyouf6jcOIPe/BgwG/d7hKjzMKOas33/pHEr6gbq18IK9zV7DiPvzsz0oBJPme6qr6H6kGZuI9/DZg==} - engines: {node: '>= 6.13.0'} - node-gyp-build-optional-packages@5.2.2: resolution: {integrity: sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==} hasBin: true @@ -9590,16 +9741,16 @@ packages: resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} hasBin: true - node-gyp@12.2.0: - resolution: {integrity: sha512-q23WdzrQv48KozXlr0U1v9dwO/k59NHeSzn6loGcasyf0UnSrtzs8kRxM+mfwJSf0DkX0s43hcqgnSO4/VNthQ==} + node-gyp@12.3.0: + resolution: {integrity: sha512-QNcUWM+HgJplcPzBvFBZ9VXacyGZ4+VTOb80PwWR+TlVzoHbRKULNEzpRsnaoxG3Wzr7Qh7BYxGDU3CbKib2Yg==} engines: {node: ^20.17.0 || >=22.9.0} hasBin: true - node-releases@2.0.38: - resolution: {integrity: sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==} + node-releases@2.0.44: + resolution: {integrity: sha512-5WUyunoPMsvvEhS8AxHtRzP+oA8UCkJ7YRxatWKjngndhDGLiqEVAQKWjFAiAiuL8zMRGzGSJxFnLetoa43qGQ==} - nodemailer@8.0.5: - resolution: {integrity: sha512-0PF8Yb1yZuQfQbq+5/pZJrtF6WQcjTd5/S4JOHs9PGFxuTqoB/icwuB44pOdURHJbRKX1PPoJZtY7R4VUoCC8w==} + nodemailer@8.0.7: + resolution: {integrity: sha512-pkjE4mkBzQjdJT4/UmlKl3pX0rC9fZmjh7c6C9o7lv66Ac6w9WCnzPzhbPNxwZAzlF4mdq4CSWB5+FbK6FWCow==} engines: {node: '>=6.0.0'} nopt@1.0.10: @@ -9620,8 +9771,8 @@ packages: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} - normalize-url@8.1.0: - resolution: {integrity: sha512-X06Mfd/5aKsRHc0O0J5CUedwnPmnDtLF2+nq+KN9KSDlJHkPuh0JUviWjEWMe0SW/9TDdSLVPuk7L5gGTIA1/w==} + normalize-url@8.1.1: + resolution: {integrity: sha512-JYc0DPlpGWB40kH5g07gGTrYuMqV653k3uBKY6uITPWds3M0ov3GaWGp9lbE3Bzngx8+XkfzgvASb9vk9JDFXQ==} engines: {node: '>=14.16'} not@0.1.0: @@ -9658,8 +9809,8 @@ packages: engines: {node: '>=18'} hasBin: true - oauth4webapi@3.8.5: - resolution: {integrity: sha512-A8jmyUckVhRJj5lspguklcl90Ydqk61H3dcU0oLhH3Yv13KpAliKTt5hknpGGPZSSfOwGyraNEFmofDYH+1kSg==} + oauth4webapi@3.8.6: + resolution: {integrity: sha512-iwemM91xz8nryHti2yTmg5fhyEMVOkOXwHNqbvcATjyajb5oQxCQzrNOA6uElRHuMhQQTKUyFKV9y/CNyg25BQ==} object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} @@ -9690,8 +9841,8 @@ packages: obug@2.1.1: resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} - oidc-provider@9.8.2: - resolution: {integrity: sha512-Iu/VahRoAhgmzKdvqSX/4ZzrG11Zf6NHuhu1wLkoblBnMUIwud++D2lftK8jV/gLhRl3Fppa3RINYCf/675cjw==} + oidc-provider@9.8.3: + resolution: {integrity: sha512-YkchaAyVAZbsn/l7IQhcEMdeDL3lwSo/PNUtnsXSqPqT7EG8DRko0EAWzHd/n9VfCtKVkxGjYOY4h4UwFcWnUA==} on-finished@2.4.1: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} @@ -9728,8 +9879,8 @@ packages: resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==} hasBin: true - openid-client@6.8.3: - resolution: {integrity: sha512-AoY/NaN9esS3+xvHInFSK0g3skSfeE0uqQAKRj4rB6/GsBIvzwTUaYo9+HcqpKIaP0dP85p5W07hayKgS4GAeA==} + openid-client@6.8.4: + resolution: {integrity: sha512-QSw0BA08piujetEwfZsHoTrDpMEha7GDZDicQqVwX4u0ChCjefvjDB++TZ8BTg76UpwhzIQgdvvfgfl3HpCSAw==} optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} @@ -9779,10 +9930,6 @@ packages: resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} engines: {node: '>=10'} - p-map@7.0.4: - resolution: {integrity: sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==} - engines: {node: '>=18'} - p-queue@6.6.2: resolution: {integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==} engines: {node: '>=8'} @@ -9921,30 +10068,30 @@ packages: peberminta@0.9.0: resolution: {integrity: sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==} - pg-cloudflare@1.3.0: - resolution: {integrity: sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==} + pg-cloudflare@1.4.0: + resolution: {integrity: sha512-Vo7z/6rrQYxpNRylp4Tlob2elzbh+N/MOQbxFVWCxS7oEx6jF53GTJFxK2WWpKuBRkmiin4Mt+xofFDjx09R0A==} - pg-connection-string@2.12.0: - resolution: {integrity: sha512-U7qg+bpswf3Cs5xLzRqbXbQl85ng0mfSV/J0nnA31MCLgvEaAo7CIhmeyrmJpOr7o+zm0rXK+hNnT5l9RHkCkQ==} + pg-connection-string@2.13.0: + resolution: {integrity: sha512-EMnU9E2fSULdsbErBbMaXJvFeD9B4+nPcM3f+4lsiCR0BHLPrLVjv3DbyM2hgQQviKJaTWIRRTjKjWlHg3p2ig==} pg-int8@1.0.1: resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} engines: {node: '>=4.0.0'} - pg-pool@3.13.0: - resolution: {integrity: sha512-gB+R+Xud1gLFuRD/QgOIgGOBE2KCQPaPwkzBBGC9oG69pHTkhQeIuejVIk3/cnDyX39av2AxomQiyPT13WKHQA==} + pg-pool@3.14.0: + resolution: {integrity: sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==} peerDependencies: pg: '>=8.0' - pg-protocol@1.13.0: - resolution: {integrity: sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w==} + pg-protocol@1.14.0: + resolution: {integrity: sha512-n5taZ1kO3s9ngDTVxsEznOqCyToTgz0FLuPq0B33COy5pPpuWJpY3/2oRBVETuOgzdqRXfWpM9HIhp2LBBT1BA==} pg-types@2.2.0: resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} engines: {node: '>=4'} - pg@8.20.0: - resolution: {integrity: sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==} + pg@8.21.0: + resolution: {integrity: sha512-AUP1EYJuHraQGsVoCQVIcM7TEJVGtDzxWtGFZd8rds9d+CCXlU5Js1rYgfLNvxy9iJrpHjGrRjoi/3BT9fRyiA==} engines: {node: '>= 16.0.0'} peerDependencies: pg-native: '>=3.0.1' @@ -9978,19 +10125,24 @@ packages: resolution: {integrity: sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==} engines: {node: '>=14.16'} - pkg-types@1.3.1: - resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + pkijs@3.4.0: + resolution: {integrity: sha512-emEcLuomt2j03vxD54giVB4SxTjnsqkU692xZOZXHDVoYyypEm+b3jpiTcc+Cf+myooc+/Ly0z01jqeNHVgJGw==} + engines: {node: '>=16.0.0'} - playwright-core@1.59.1: - resolution: {integrity: sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==} + playwright-core@1.60.0: + resolution: {integrity: sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==} engines: {node: '>=18'} hasBin: true - playwright@1.59.1: - resolution: {integrity: sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw==} + playwright@1.60.0: + resolution: {integrity: sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==} engines: {node: '>=18'} hasBin: true + plimit-lit@1.6.1: + resolution: {integrity: sha512-B7+VDyb8Tl6oMJT9oSO2CW8XC/T4UcJGrwOVoNGwOQsQYhlpfajmrMj5xeejqaASq3V/EqThyOeATEOMuSEXiA==} + engines: {node: '>=12'} + pluralize@8.0.0: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} engines: {node: '>=4'} @@ -10377,8 +10529,8 @@ packages: peerDependencies: postcss: ^8.4 - postcss-preset-env@10.5.0: - resolution: {integrity: sha512-xgxFQPAPxeWmsgy8cR7GM1PGAL/smA5E9qU7K//D4vucS01es3M0fDujhDJn3kY8Ip7/vVYcecbe1yY+vBo3qQ==} + postcss-preset-env@10.6.1: + resolution: {integrity: sha512-yrk74d9EvY+W7+lO9Aj1QmjWY9q5NsKjK2V9drkOPZB/X6KZ0B3igKsHUYakb7oYVhnioWypQX3xGuePf89f3g==} engines: {node: '>=18'} peerDependencies: postcss: ^8.4 @@ -10465,8 +10617,8 @@ packages: peerDependencies: postcss: ^8.4.31 - postcss@8.5.12: - resolution: {integrity: sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA==} + postcss@8.5.15: + resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} engines: {node: ^10 || ^12 || >=14} postgres-array@2.0.0: @@ -10496,6 +10648,12 @@ packages: resolution: {integrity: sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==} engines: {node: '>=20'} + prebuild-install@7.1.3: + resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==} + engines: {node: '>=10'} + deprecated: No longer maintained. Please contact the author of the relevant native addon; alternatives are available. + hasBin: true + prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -10520,11 +10678,12 @@ packages: peerDependencies: prettier: ^3.0.0 - prettier-plugin-svelte@3.5.1: - resolution: {integrity: sha512-65+fr5+cgIKWKiqM1Doum4uX6bY8iFCdztvvp2RcF+AJoieaw9kJOFMNcJo/bkmKYsxFaM9OsVZK/gWauG/5mg==} + prettier-plugin-svelte@4.1.0: + resolution: {integrity: sha512-YZkhA2Q9oOerFFG9tq+2f98WYT7Z2JgrybJrAyrB78jpsH9i/DdgplXemehuFPgsldetFNCcR/yCcYlDjPy94Q==} + engines: {node: '>=20'} peerDependencies: prettier: ^3.0.0 - svelte: ^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0 + svelte: ^5.0.0 prettier@3.8.3: resolution: {integrity: sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==} @@ -10562,10 +10721,6 @@ packages: resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} engines: {node: '>= 0.6.0'} - promise-retry@2.0.1: - resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} - engines: {node: '>=10'} - prompts@2.4.2: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} engines: {node: '>= 6'} @@ -10589,12 +10744,8 @@ packages: proto-list@1.2.4: resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} - protobufjs@7.5.5: - resolution: {integrity: sha512-3wY1AxV+VBNW8Yypfd1yQY9pXnqTAN+KwQxL8iYm3/BjKYMNg4i0owhEe26PWDOMaIrzeeF98Lqd5NGz4omiIg==} - engines: {node: '>=12.0.0'} - - protobufjs@8.0.1: - resolution: {integrity: sha512-NWWCCscLjs+cOKF/s/XVNFRW7Yih0fdH+9brffR5NZCy8k42yRdl5KlWKMVXuI1vfCoy4o1z80XR/W/QUb3V3w==} + protobufjs@7.6.0: + resolution: {integrity: sha512-LtESOsMPTZgyYtwxhvdgdjGL0HmXEaRA/hVD6sol4zA60hVXXXP/SGmxnqDbgGE8gy7pYex7cym+5vYPcmaXBQ==} engines: {node: '>=12.0.0'} protocol-buffers-schema@3.6.1: @@ -10618,15 +10769,26 @@ packages: resolution: {integrity: sha512-LjgDO2zPtoXP2wJpDjZrGdojii1uqO0cnwKoIoUzkfS98HDmbeiGmYiXo3lXeFlq2xvne1QFQhwYXSUCLKtEuA==} engines: {node: '>=12.20'} + pvtsutils@1.3.6: + resolution: {integrity: sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==} + + pvutils@1.1.5: + resolution: {integrity: sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA==} + engines: {node: '>=16.0.0'} + qrcode@1.5.4: resolution: {integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==} engines: {node: '>=10.13.0'} hasBin: true - qs@6.14.1: - resolution: {integrity: sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==} + qs@6.15.2: + resolution: {integrity: sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==} engines: {node: '>=0.6'} + queue-lit@1.5.2: + resolution: {integrity: sha512-tLc36IOPeMAubu8BkW8YDBV+WyIgKlYU7zUNs0J5Vk9skSZ4JfGlPOqplP0aHdfv7HL0B2Pg6nwiq60Qc6M2Hw==} + engines: {node: '>=12'} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -10677,10 +10839,10 @@ packages: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true - react-dom@19.2.5: - resolution: {integrity: sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==} + react-dom@19.2.6: + resolution: {integrity: sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==} peerDependencies: - react: ^19.2.5 + react: ^19.2.6 react-email@5.2.11: resolution: {integrity: sha512-9TzTGRGeavli/iv1RICVOePnFOeG2YRyr8kAyXj6Zgudteq60uA0txDwe4q1zIjQ+08fcbzBkSnI9HgPkSK5OA==} @@ -10725,8 +10887,8 @@ packages: peerDependencies: react: '>=15' - react@19.2.5: - resolution: {integrity: sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==} + react@19.2.6: + resolution: {integrity: sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==} engines: {node: '>=0.10.0'} read-cache@1.0.0: @@ -10798,8 +10960,8 @@ packages: resolution: {integrity: sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==} engines: {node: '>=4'} - registry-auth-token@5.1.0: - resolution: {integrity: sha512-GdekYuwLXLxMuFTwAPg5UKGLW/UXzQrZvH/Zj791BQif5T05T0RsaLfHc9q3ZOKi7n+BoprPD9mJ0O0k4xzUlw==} + registry-auth-token@5.1.1: + resolution: {integrity: sha512-P7B4+jq8DeD2nMsAcdfaqHbssgHtZ7Z5+++a5ask90fvmJ8p5je4mOa+wzu+DB4vQ5tdJV/xywY+UnVFeQLV5Q==} engines: {node: '>=14'} registry-url@6.0.1: @@ -10809,8 +10971,8 @@ packages: regjsgen@0.8.0: resolution: {integrity: sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==} - regjsparser@0.13.0: - resolution: {integrity: sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==} + regjsparser@0.13.1: + resolution: {integrity: sha512-dLsljMd9sqwRkby8zhO1gSg3PnJIBFid8f4CQj/sXx+7cKx+E7u0PKhZ+U4wmhx7EfmtvnA318oVaIkAB1lRJw==} hasBin: true rehype-parse@7.0.1: @@ -10895,15 +11057,11 @@ packages: resolve-protobuf-schema@2.1.0: resolution: {integrity: sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==} - resolve@1.22.11: - resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} + resolve@1.22.12: + resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} engines: {node: '>= 0.4'} hasBin: true - response-time@2.3.4: - resolution: {integrity: sha512-fiyq1RvW5/Br6iAtT8jN1XrNY8WPu2+yEypLbaijWry8WDZmn12azG9p/+c+qpEebURLlQmqCB8BNSu7ji+xQQ==} - engines: {node: '>= 0.8.0'} - responselike@3.0.0: resolution: {integrity: sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==} engines: {node: '>=14.16'} @@ -10940,8 +11098,8 @@ packages: robust-predicates@3.0.3: resolution: {integrity: sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==} - rolldown@1.0.0-rc.17: - resolution: {integrity: sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA==} + rolldown@1.0.1: + resolution: {integrity: sha512-X0KQHljNnEkWNqqiz9zJrGunh1B0HgOxLXvnFpCOcadzcy5qohZ3tqMEUg00vncoRovXuK3ZqCT9KnnKzoInFQ==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true @@ -10958,8 +11116,8 @@ packages: rollup: optional: true - rollup@4.55.1: - resolution: {integrity: sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==} + rollup@4.60.4: + resolution: {integrity: sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -11028,8 +11186,9 @@ packages: engines: {node: '>=14.0.0'} hasBin: true - sax@1.4.3: - resolution: {integrity: sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==} + sax@1.6.0: + resolution: {integrity: sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==} + engines: {node: '>=11.0.0'} saxes@6.0.0: resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} @@ -11062,9 +11221,9 @@ packages: select-hose@2.0.0: resolution: {integrity: sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==} - selfsigned@2.4.1: - resolution: {integrity: sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==} - engines: {node: '>=10'} + selfsigned@5.5.0: + resolution: {integrity: sha512-ftnu3TW4+3eBfLRFnDEkzGxSF/10BJBkaLJuBHZX0kiPS7bRdlpZGu6YGt4KngMkdTwJE6MbjavFpqHvqVt+Ew==} + engines: {node: '>=18'} semver-diff@4.0.0: resolution: {integrity: sha512-0Ju4+6A8iOnpL/Thra7dZsSlOHYAHIeMxfhWQRI1/VLcT3WDBZKKtQt/QkBOsiIN9ZpuvHE6cGZ0x4glCMmfiA==} @@ -11074,8 +11233,13 @@ packages: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true - semver@7.7.4: - resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + semver@7.8.0: + resolution: {integrity: sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==} + engines: {node: '>=10'} + hasBin: true + + semver@7.8.1: + resolution: {integrity: sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==} engines: {node: '>=10'} hasBin: true @@ -11093,8 +11257,8 @@ packages: serve-handler@6.1.7: resolution: {integrity: sha512-CinAq1xWb0vR3twAv9evEU8cNWkXCb9kd5ePAHUKJBkOsUpR1wt/CvGdeca7vqumL1U5cSaeVQ6zZMxiJ3yWsg==} - serve-index@1.9.1: - resolution: {integrity: sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==} + serve-index@1.9.2: + resolution: {integrity: sha512-KDj11HScOaLmrPxl70KYNW1PksP4Nb/CLL2yvC+Qd2kHMPEEpfc4Re2e4FOay+bC/+XQl/7zAcWON3JVo5v3KQ==} engines: {node: '>= 0.8.0'} serve-static@1.16.3: @@ -11115,9 +11279,6 @@ packages: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} - setprototypeof@1.1.0: - resolution: {integrity: sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==} - setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} @@ -11148,8 +11309,8 @@ packages: resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==} engines: {node: '>= 0.4'} - side-channel-list@1.0.0: - resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} engines: {node: '>= 0.4'} side-channel-map@1.0.1: @@ -11177,11 +11338,11 @@ packages: simple-concat@1.0.1: resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} - simple-get@3.1.1: - resolution: {integrity: sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==} + simple-get@4.0.1: + resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} - simple-icons@16.17.0: - resolution: {integrity: sha512-bRrGtzM6NLgxeMWmRcfDdrRksECk101lRrCn6jjj6qzUB6lQ+E5smnr52rqS1kLPmbLpS/g6iF463j50M4BT7A==} + simple-icons@16.20.0: + resolution: {integrity: sha512-2b7Xu+Zy+iwLiuLSAcWuxnM1ETNFy7JmxWSjh0Y/A8P3KGjFFcokbpWbUf/IivMNbR9WuZ/QQlirh5bGh3Q2vA==} engines: {node: '>=0.12.18'} sirv@2.0.4: @@ -11195,8 +11356,8 @@ packages: sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} - sitemap@7.1.2: - resolution: {integrity: sha512-ARCqzHJ0p4gWt+j7NlU5eDlIO9+Rkr/JhPFZKKQ1l5GCus7rJH4UdrlVAh0xC/gDS/Qir2UMxqYNHtsKr2rpCw==} + sitemap@7.1.3: + resolution: {integrity: sha512-tAjEd+wt/YwnEbfNB2ht51ybBJxbEWwe5ki/Z//Wh0rpBFTCUSj46GnxUKEWzhfuJTsee8x3lybHxFgUMig2hw==} engines: {node: '>=12.0.0', npm: '>=5.6.0'} hasBin: true @@ -11215,22 +11376,18 @@ packages: slice-source@0.4.1: resolution: {integrity: sha512-YiuPbxpCj4hD9Qs06hGAz/OZhQ0eDuALN0lRWJez0eD/RevzKqGdUx1IOMUnXgpr+sXZLq3g8ERwbAH0bCb8vg==} - smart-buffer@4.2.0: - resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} - engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} - snake-case@3.0.4: resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} - socket.io-adapter@2.5.6: - resolution: {integrity: sha512-DkkO/dz7MGln0dHn5bmN3pPy+JmywNICWrJqVWiVOyvXjWQFIv9c2h24JrQLLFJ2aQVQf/Cvl1vblnd4r2apLQ==} + socket.io-adapter@2.5.7: + resolution: {integrity: sha512-e0LyK91f3cUxTmv95/KzoLg47+zF+s/sbxRGDNsyG4dmIP8ZSX8ax6byOxfJXeNNtS/8AZlfD+uP7gBeR7DLlg==} socket.io-client@4.8.3: resolution: {integrity: sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g==} engines: {node: '>=10.0.0'} - socket.io-parser@4.2.5: - resolution: {integrity: sha512-bPMmpy/5WWKHea5Y/jYAP6k74A+hvmRCQaJuJB6I/ML5JZq/KfNieUVo/3Mh7SAqn7TyFdIo6wqYHInG1MU1bQ==} + socket.io-parser@4.2.6: + resolution: {integrity: sha512-asJqbVBDsBCJx0pTqw3WfesSY0iRX+2xzWEWzrpcH7L6fLzrhyF8WPI8UaeM4YCuDfpwA/cgsdugMsmtz8EJeg==} engines: {node: '>=10.0.0'} socket.io@4.8.3: @@ -11240,14 +11397,6 @@ packages: sockjs@0.3.24: resolution: {integrity: sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==} - socks-proxy-agent@8.0.5: - resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==} - engines: {node: '>= 14'} - - socks@2.8.7: - resolution: {integrity: sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==} - engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} - sort-css-media-queries@2.2.0: resolution: {integrity: sha512-0xtkGhWCC9MGt/EzgnvbbbKhqWjl1+/rncmhTh5qCpbYguXh6S/qwePfv/JQ8jePXXmqingylxoC49pCkSPIbA==} engines: {node: '>= 6.3.0'} @@ -11294,8 +11443,8 @@ packages: sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} - sql-formatter@15.7.3: - resolution: {integrity: sha512-5+zl9Nqg5aNjss0tb1G+StpC4dJKbjv3+g8CL/+V+00PfZop+2RKGyi53ScFl0dr+Dkx1LjmUO54Q3N7K3EtMw==} + sql-formatter@15.8.0: + resolution: {integrity: sha512-HnjdRHlSsO4Ap2erB5YXAvWggrnk/S4TezUn8zmpq9J/hEKn9+6gGaqiKPyDtI10Xf4zJmHYPREGjMjZmmP1fg==} hasBin: true srcset@4.0.0: @@ -11309,10 +11458,6 @@ packages: resolution: {integrity: sha512-wPldCk3asibAjQ/kziWQQt1Wh3PgDFpC0XpwclzKcdT1vql6KeYxf5LIt4nlFkUeR8WuphYMKqUA56X4rjbfgQ==} engines: {node: '>=10.16.0'} - ssri@13.0.1: - resolution: {integrity: sha512-QUiRf1+u9wPTL/76GTYlKttDEBWV1ga9ZXW8BG6kfdeyyM8LGPix9gROyg9V2+P0xNyF3X2Go526xKFdMZrHSQ==} - engines: {node: ^20.17.0 || >=22.9.0} - stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} @@ -11436,8 +11581,8 @@ packages: peerDependencies: postcss: ^8.4.31 - stylis@4.3.6: - resolution: {integrity: sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==} + stylis@4.4.0: + resolution: {integrity: sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==} sucrase@3.35.1: resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==} @@ -11472,17 +11617,17 @@ packages: peerDependencies: svelte: '>= 3.43.1 < 6' - svelte-check@4.4.6: - resolution: {integrity: sha512-kP1zG81EWaFe9ZyTv4ZXv44Csi6Pkdpb7S3oj6m+K2ec/IcDg/a8LsFsnVLqm2nxtkSwsd5xPj/qFkTBgXHXjg==} + svelte-check@4.4.8: + resolution: {integrity: sha512-67adfgBox5eNSNIvIIwgFizKGdcRrGpiMoNO2obHcYuLz7iTa8Xgm/NGU3ntMFnNm8K1grFOIG6HhMLX/vcN8w==} engines: {node: '>= 18.0.0'} hasBin: true peerDependencies: svelte: ^4.0.0 || ^5.0.0-next.0 typescript: '>=5.0.0' - svelte-eslint-parser@1.6.0: - resolution: {integrity: sha512-qoB1ehychT6OxEtQAqc/guSqLS20SlA53Uijl7x375s8nlUT0lb9ol/gzraEEatQwsyPTJo87s2CmKL9Xab+Uw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0, pnpm: 10.30.3} + svelte-eslint-parser@1.6.1: + resolution: {integrity: sha512-hhvSH6kRj46UzrBVO5TaotD+Iuvruj5ccKBcO4wAhVcPTLmIc/c32D8UllBTYO0on4LzYuM0rNzf1lM/gBlkSQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0, pnpm: 10.33.0} peerDependencies: svelte: ^3.37.0 || ^4.0.0 || ^5.0.0 peerDependenciesMeta: @@ -11548,25 +11693,20 @@ packages: peerDependencies: svelte: ^5.30.2 - svelte@5.55.2: - resolution: {integrity: sha512-z41M/hi0ZPTzrwVKLvB/R1/Oo08gL1uIib8HZ+FncqxxtY9MLb01emg2fqk+WLZ/lNrrtNDFh7BZLDxAHvMgLw==} + svelte@5.55.8: + resolution: {integrity: sha512-4D6lyrMHmDaZalQOEBMCWCCidyZjSnec14/oPn0k627G6goxcck9xqMwz1tFLlQz+ZFvtTTHfFOlUayuAz0z6Q==} engines: {node: '>=18'} svg-parser@2.0.4: resolution: {integrity: sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==} - svgo@3.3.2: - resolution: {integrity: sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==} + svgo@3.3.3: + resolution: {integrity: sha512-+wn7I4p7YgJhHs38k2TNjy1vCfPIfLIJWR5MnCStsN8WuuTcBnRKcMHQLMM2ijxGZmDoZwNv8ipl5aTTen62ng==} engines: {node: '>=14.0.0'} hasBin: true - swagger-ui-dist@5.32.4: - resolution: {integrity: sha512-0AADFFQNJzExEN49SrD/34Nn9cxNxVLiydYl2MBwSZFPVXNkVwC/EFAjoezGGqE8oDegiDC+p47t8lKObCinMQ==} - - swr@2.3.8: - resolution: {integrity: sha512-gaCPRVoMq8WGDcWj9p4YWzCMPHzE0WNl6W8ADIx9c3JBEIdMkJGMzW+uzXvxHMltwcYACr9jP+32H8/hgwMR7w==} - peerDependencies: - react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + swagger-ui-dist@5.32.6: + resolution: {integrity: sha512-75ttZNaYCLoFPnozPZcTUU6mS3wKT8l7WLjU5zJSHFeJa23i5vtnze6IiCl4jDMPeQTXVXIgovq4M11NNfQvSA==} symbol-observable@4.0.0: resolution: {integrity: sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==} @@ -11579,8 +11719,8 @@ packages: resolution: {integrity: sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==} engines: {node: ^14.18.0 || >=16.0.0} - systeminformation@5.23.8: - resolution: {integrity: sha512-Osd24mNKe6jr/YoXLLK3k8TMdzaxDffhpCxgkfgBHcapykIkd50HXThM3TCEuHO2pPuCsSx2ms/SunqhU5MmsQ==} + systeminformation@5.31.6: + resolution: {integrity: sha512-Uv2b2uGGM6ns+26czgW2cYRabYdnswM0ddSOOlryHOaelzsmDSet1iM/NT7VOYxW8x/BW+HkY+b1Ve2pLTSGSA==} engines: {node: '>=8.0.0'} os: [darwin, linux, win32, freebsd, openbsd, netbsd, sunos, android] hasBin: true @@ -11601,8 +11741,8 @@ packages: '@eslint/css': optional: true - tailwind-merge@3.5.0: - resolution: {integrity: sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==} + tailwind-merge@3.6.0: + resolution: {integrity: sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==} tailwind-variants@3.2.2: resolution: {integrity: sha512-Mi4kHeMTLvKlM98XPnK+7HoBPmf4gygdFmqQPaDivc3DpYS6aIY6KiG/PgThrGvii5YZJqRsPz0aPyhoFzmZgg==} @@ -11636,8 +11776,8 @@ packages: engines: {node: '>=14.0.0'} hasBin: true - tailwindcss@4.2.4: - resolution: {integrity: sha512-HhKppgO81FQof5m6TEnuBWCZGgfRAWbaeOaGT00KOy/Pf/j6oUihdvBpA7ltCeAvZpFhW3j0PTclkxsd4IXYDA==} + tailwindcss@4.3.0: + resolution: {integrity: sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==} tapable@2.3.3: resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} @@ -11653,49 +11793,71 @@ packages: resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} engines: {node: '>=6'} - tar-stream@3.1.8: - resolution: {integrity: sha512-U6QpVRyCGHva435KoNWy9PRoi2IFYCgtEhq9nmrPPpbRacPs9IH4aJ3gbrFC8dPcXvdSZ4XXfXT5Fshbp2MtlQ==} + tar-stream@3.2.0: + resolution: {integrity: sha512-ojzvCvVaNp6aOTFmG7jaRD0meowIAuPc3cMMhSgKiVWws1GyHbGd/xvnyuRKcKlMpt3qvxx6r0hreCNITP9hIg==} tar@6.2.1: resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} engines: {node: '>=10'} deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me - tar@7.5.7: - resolution: {integrity: sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==} + tar@7.5.15: + resolution: {integrity: sha512-dzGK0boVlC4W5QFuQN1EFSl3bIDYsk7Tj40U6eIBnK2k/8ml7TZ5agbI5j5+qnoVcAA+rNtBml8SEiLxZpNqRQ==} engines: {node: '>=18'} - deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me teex@1.0.1: resolution: {integrity: sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==} - terser-webpack-plugin@5.4.0: - resolution: {integrity: sha512-Bn5vxm48flOIfkdl5CaD2+1CiUVbonWQ3KQPyP7/EuIl9Gbzq/gQFOzaMFUEgVjB1396tcK0SG8XcNJ/2kDH8g==} + terser-webpack-plugin@5.6.0: + resolution: {integrity: sha512-Eum+5ajkaOhf5KbM26osvv21kLD7BaGqQ1UA4Ami4arYwylmGUQTgHFpHDdmJod1q4QXa66p0to/FBKID+J1vA==} engines: {node: '>= 10.13.0'} peerDependencies: + '@minify-html/node': '*' '@swc/core': '*' + '@swc/css': '*' + '@swc/html': '*' + clean-css: '*' + cssnano: '*' + csso: '*' esbuild: '*' + html-minifier-terser: '*' + lightningcss: '*' + postcss: '*' uglify-js: '*' webpack: ^5.1.0 peerDependenciesMeta: + '@minify-html/node': + optional: true '@swc/core': optional: true + '@swc/css': + optional: true + '@swc/html': + optional: true + clean-css: + optional: true + cssnano: + optional: true + csso: + optional: true esbuild: optional: true + html-minifier-terser: + optional: true + lightningcss: + optional: true + postcss: + optional: true uglify-js: optional: true - terser@5.46.1: - resolution: {integrity: sha512-vzCjQO/rgUuK9sf8VJZvjqiqiHFaZLnOiimmUuOKODxWL8mm/xua7viT7aqX7dgPY60otQjUotzFMmCB4VdmqQ==} + terser@5.47.1: + resolution: {integrity: sha512-tPbLXTI6ohPASb/1YViL428oEHu6/qv1OxqYnfaonVCFHqx4+wCd95pHrQWsL5X4pl90CTyW9piSAsS2L0VoMw==} engines: {node: '>=10'} hasBin: true - test-exclude@7.0.2: - resolution: {integrity: sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==} - engines: {node: '>=18'} - - testcontainers@11.14.0: - resolution: {integrity: sha512-r9pniwv/iwzyHaI7gwAvAm4Y+IvjJg3vBWdjrUCaDMc2AXIr4jKbq7jJO18Mw2ybs73pZy1Aj7p/4RVBGMRWjg==} + testcontainers@12.0.1: + resolution: {integrity: sha512-EMjjfMNJf3HlL7V3elkxqKUO1r3CtqNBTdmKGwwma/lOtUGfoWvFJ0WQ/KQf1DHEMnRjLWzW4cXbv/Tndsbcbw==} text-decoder@1.2.7: resolution: {integrity: sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==} @@ -11711,8 +11873,8 @@ packages: thenify@3.3.1: resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} - thingies@2.5.0: - resolution: {integrity: sha512-s+2Bwztg6PhWUD7XMfeYm5qliDdSiZm7M7n8KjTkIsm3l/2lgVRc2/Gx/v+ZX8lT4FMA+i8aQvhcWylldc+ZNw==} + thingies@2.6.0: + resolution: {integrity: sha512-rMHRjmlFLM1R96UYPvpmnc3LYtdFrT33JIB7L9hetGue1qAPfn1N2LJeEjxUSidu1Iku+haLZXDuEXUHNGO/lg==} engines: {node: '>=10.18'} peerDependencies: tslib: ^2 @@ -11720,12 +11882,8 @@ packages: three@0.179.1: resolution: {integrity: sha512-5y/elSIQbrvKOISxpwXCR4sQqHtGiOI+MKLc3SsBdDXA2hz3Mdp3X59aUp8DyybMa34aeBwbFTpdoLJaUDEWSw==} - three@0.182.0: - resolution: {integrity: sha512-GbHabT+Irv+ihI1/f5kIIsZ+Ef9Sl5A1Y7imvS5RQjWgtTPfPnZ43JmlYI7NtCRDK9zir20lQpfg8/9Yd02OvQ==} - - throttleit@2.1.0: - resolution: {integrity: sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==} - engines: {node: '>=18'} + three@0.184.0: + resolution: {integrity: sha512-wtTRjG92pM5eUg/KuUnHsqSAlPM296brTOcLgMRqEeylYTh/CdtvKUvCyyCQTzFuStieWxvZb8mVTMvdPyUpxg==} through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} @@ -11755,8 +11913,8 @@ packages: tinyexec@0.3.2: resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} - tinyexec@1.1.1: - resolution: {integrity: sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==} + tinyexec@1.1.2: + resolution: {integrity: sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==} engines: {node: '>=18'} tinyglobby@0.2.16: @@ -11789,8 +11947,8 @@ packages: resolution: {integrity: sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==} hasBin: true - tmp@0.2.5: - resolution: {integrity: sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==} + tmp@0.2.7: + resolution: {integrity: sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==} engines: {node: '>=14.14'} to-regex-range@5.0.1: @@ -11857,6 +12015,11 @@ packages: ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + tsc-alias@1.8.17: + resolution: {integrity: sha512-EIduCZHqbNwPm8BZYfq1aD7BQ697A4h6uSGMOFQfYGoQwfrYFTKwYfy9Bv42YxHkduVBcn9Zx0DkX111DKskyg==} + engines: {node: '>=16.20.2'} + hasBin: true + tsconfck@3.1.6: resolution: {integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==} engines: {node: ^18 || >=20} @@ -11875,6 +12038,9 @@ packages: resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} engines: {node: '>=6'} + tslib@1.14.1: + resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -11882,11 +12048,18 @@ packages: resolution: {integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==} engines: {node: '>=0.6.x'} - tsx@4.21.0: - resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} + tsx@4.22.3: + resolution: {integrity: sha512-mdoNxBC/cSQObGGVQ5Bpn5i+yv7j68gk3Nfm3wFjcJg3Z0Mix9jzAFfP12prmm5eVGmDKtp0yyArrs0Q+8gZHg==} engines: {node: '>=18.0.0'} hasBin: true + tsyringe@4.10.0: + resolution: {integrity: sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw==} + engines: {node: '>= 6.0.0'} + + tunnel-agent@0.6.0: + resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + tweetnacl@0.14.5: resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==} @@ -11914,9 +12087,9 @@ packages: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} - type-is@2.0.1: - resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} - engines: {node: '>= 0.6'} + type-is@2.1.0: + resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} + engines: {node: '>= 18'} type@2.7.3: resolution: {integrity: sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==} @@ -11927,8 +12100,8 @@ packages: typedarray@0.0.6: resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} - typescript-eslint@8.59.0: - resolution: {integrity: sha512-BU3ONW9X+v90EcCH9ZS6LMackcVtxRLlI3XrYyqZIwVSHIk7Qf7bFw1z0M9Q0IUxhTMZCf8piY9hTYaNEIASrw==} + typescript-eslint@8.59.4: + resolution: {integrity: sha512-Rw6+44QNFaXtgHSjPy+Kw8hrJniMYzR85E9yLmOLcfZ91/rz+JXQbDTCmc6ccxMPY6K6PgAq26f0JCBfR7LIPQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 @@ -11951,9 +12124,6 @@ packages: resolution: {integrity: sha512-OsqGhxyo/wGdLSXMSJxuMGN6H4gDnKz6Fb3IBm4bxZFMnyy0sdf6MN96Ie8tC6z/btdO+Bsy8guxlvLdwT076w==} hasBin: true - ufo@1.6.2: - resolution: {integrity: sha512-heMioaxBcG9+Znsda5Q8sQbWnLJSl98AFDXTO80wELWEzX3hordXsTdxrIfMQoO9IY1MEnoGoPjpoKpMj+Yx0Q==} - uglify-js@3.19.3: resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} engines: {node: '>=0.8.0'} @@ -11977,8 +12147,9 @@ packages: undici-types@7.16.0: resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} - undici-types@7.19.2: - resolution: {integrity: sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==} + undici@6.25.0: + resolution: {integrity: sha512-ZgpWDC5gmNiuY9CnLVXEH8rl50xhRCuLNA97fAUnKi8RRuV4E6KG31pDTsLVUKnohJE0I3XDrTeEydAXRw47xg==} + engines: {node: '>=18.17'} undici@7.25.0: resolution: {integrity: sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==} @@ -12010,14 +12181,6 @@ packages: unified@9.2.2: resolution: {integrity: sha512-Sg7j110mtefBD+qunSLO1lqOEKdrwBFBrR6Qd8f4uwkhWNlbkaqwHse6e7QvD3AP/MNoJdEDLaf8OxYyoWgorQ==} - unique-filename@5.0.0: - resolution: {integrity: sha512-2RaJTAvAb4owyjllTfXzFClJ7WsGxlykkPvCr9pA//LD9goVq+m4PPAeBgNodGZ7nSrntT/auWpJ6Y5IFXcfjg==} - engines: {node: ^20.17.0 || >=22.9.0} - - unique-slug@6.0.0: - resolution: {integrity: sha512-4Lup7Ezn8W3d52/xBhZBVdx323ckxa7DEvd9kPQHppTkLoJXw6ltrBCyj5pnrxj0qKDxYMJ56CoxNuFCscdTiw==} - engines: {node: ^20.17.0 || >=22.9.0} - unique-string@3.0.0: resolution: {integrity: sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==} engines: {node: '>=12'} @@ -12052,8 +12215,8 @@ packages: unist-util-visit@2.0.3: resolution: {integrity: sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==} - unist-util-visit@5.0.0: - resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} + unist-util-visit@5.1.0: + resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} universalify@2.0.1: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} @@ -12105,11 +12268,6 @@ packages: urlpattern-polyfill@8.0.2: resolution: {integrity: sha512-Qp95D4TPJl1kC9SKigDcqgyM2VDVO4RiJc2d4qe5GrYm+zbIQCWWKAFaJNQ4BhdFeDGwBmAxqJBwWSJDb9T3BQ==} - use-sync-external-store@1.6.0: - resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - utf8-byte-length@1.0.5: resolution: {integrity: sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==} @@ -12131,24 +12289,17 @@ packages: resolution: {integrity: sha512-6S5mCapmzcxetOD/2UEjL0GF5e4+gB07Dh8qs63xylw5ay4XuyW6iQs70FOJo/puf10LCkvhp4jYMQSDUBYEFg==} engines: {node: '>=10.0.0'} - uuid@10.0.0: - resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==} - hasBin: true - - uuid@11.1.0: - resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} - hasBin: true - uuid@14.0.0: resolution: {integrity: sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg==} hasBin: true uuid@8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). hasBin: true - valibot@1.3.1: - resolution: {integrity: sha512-sfdRir/QFM0JaF22hqTroPc5xy4DimuGQVKFrzF1YfGwaS1nJot3Y8VqMdLO2Lg27fMzat2yD3pY5PbAYO39Gg==} + valibot@1.4.0: + resolution: {integrity: sha512-iC/x7fVcSyOwlm/VSt7RlHnzNGLGvR9GnxdifUeWoCJo0q4ZZvrVkIHC6faTlkxG47I2Y4UrFquPuVHCrOnrLg==} peerDependencies: typescript: '>=5' peerDependenciesMeta: @@ -12201,8 +12352,8 @@ packages: peerDependencies: vite: '*' - vite@7.3.2: - resolution: {integrity: sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==} + vite@7.3.3: + resolution: {integrity: sha512-/4XH147Ui7OGTjg3HbdWe5arnZQSbfuRzdr9Ec7TQi5I7R+ir0Rlc9GIvD4v0XZurELqA035KVXJXpR61xhiTA==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -12241,13 +12392,13 @@ packages: yaml: optional: true - vite@8.0.10: - resolution: {integrity: sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw==} + vite@8.0.13: + resolution: {integrity: sha512-MFtjBYgzmSxmgA4RAfjIyXWpGe1oALnjgUTzzV7QLx/TKxCzjtMH6Fd9/eVK+5Fg1qNoz5VAwsmMs/NofrmJvw==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: '@types/node': ^20.19.0 || >=22.12.0 - '@vitejs/devtools': ^0.1.0 + '@vitejs/devtools': ^0.1.18 esbuild: ^0.27.0 || ^0.28.0 jiti: '>=1.21.0' less: ^4.0.0 @@ -12284,10 +12435,10 @@ packages: yaml: optional: true - vitefu@1.1.2: - resolution: {integrity: sha512-zpKATdUbzbsycPFBN71nS2uzBUQiVnFoOrr2rvqv34S1lcAgMKKkjWleLGeiJlZ8lwCXvtWaRn7R3ZC16SYRuw==} + vitefu@1.1.3: + resolution: {integrity: sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==} peerDependencies: - vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-beta.0 + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: vite: optional: true @@ -12326,20 +12477,20 @@ packages: jsdom: optional: true - vitest@4.1.5: - resolution: {integrity: sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg==} + vitest@4.1.7: + resolution: {integrity: sha512-flYyaFd2CgoCoU+0UKt3pxksgC+S02iTDN0n3LtqaMeXsI9SBcdNujc2k0DeFLzUn/0k538yNjOSdwgCqcrwJA==} engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@opentelemetry/api': ^1.9.0 '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.1.5 - '@vitest/browser-preview': 4.1.5 - '@vitest/browser-webdriverio': 4.1.5 - '@vitest/coverage-istanbul': 4.1.5 - '@vitest/coverage-v8': 4.1.5 - '@vitest/ui': 4.1.5 + '@vitest/browser-playwright': 4.1.7 + '@vitest/browser-preview': 4.1.7 + '@vitest/browser-webdriverio': 4.1.7 + '@vitest/coverage-istanbul': 4.1.7 + '@vitest/coverage-v8': 4.1.7 + '@vitest/ui': 4.1.7 happy-dom: '*' jsdom: '*' vite: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -12367,26 +12518,6 @@ packages: jsdom: optional: true - vscode-jsonrpc@8.2.0: - resolution: {integrity: sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==} - engines: {node: '>=14.0.0'} - - vscode-languageserver-protocol@3.17.5: - resolution: {integrity: sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==} - - vscode-languageserver-textdocument@1.0.12: - resolution: {integrity: sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==} - - vscode-languageserver-types@3.17.5: - resolution: {integrity: sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==} - - vscode-languageserver@9.0.1: - resolution: {integrity: sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==} - hasBin: true - - vscode-uri@3.0.8: - resolution: {integrity: sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==} - w3c-keyname@2.2.8: resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} @@ -12431,8 +12562,8 @@ packages: webpack: optional: true - webpack-dev-server@5.2.2: - resolution: {integrity: sha512-QcQ72gh8a+7JO63TAx/6XZf/CWhgMzu5m0QirvPfGvptOusAxG12w2+aua1Jkjr7hzaWDnJ2n6JFeexMHI+Zjg==} + webpack-dev-server@5.2.4: + resolution: {integrity: sha512-GqDPGZN9bRqKBTkp4aWkobDDHMsrXKoGSdOH56smIri8qR0JG8gfL8/v/f/OZR3/OKXjG8uwJbFVhKm/FNU/UA==} engines: {node: '>= 18.12.0'} hasBin: true peerDependencies: @@ -12456,8 +12587,8 @@ packages: resolution: {integrity: sha512-LnL6Z3GGDPht/AigwRh2dvL9PQPFQ8skEpVrWZXLWBYmqcaojHNN0onvHzie6rq7EWKrrBfPYqNEzTJgiwEQDQ==} engines: {node: '>=6'} - webpack-sources@3.3.4: - resolution: {integrity: sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==} + webpack-sources@3.4.1: + resolution: {integrity: sha512-eACpxRN02yaawnt+uUNIF7Qje6A9zArxBbcAJjK1PK3S9Ycg5jIuJ8pW4q8EMnwNZCEGltcjkRx1QzOxOkKD8A==} engines: {node: '>=10.13.0'} webpack-virtual-modules@0.6.2: @@ -12473,8 +12604,8 @@ packages: webpack-cli: optional: true - webpack@5.106.2: - resolution: {integrity: sha512-wGN3qcrBQIFmQ/c0AiOAQBvrZ5lmY8vbbMv4Mxfgzqd/B6+9pXtLo73WuS1dSGXM5QYY3hZnIbvx+K1xxe6FyA==} + webpack@5.107.0: + resolution: {integrity: sha512-PSxeHk/dmLYZlnTU+vL1Gej6Evg5RNtl3flhxBresfznFnzxinHMzHKloHnywM/3ouQv7/AlZCswWDIkNSggUA==} engines: {node: '>=10.13.0'} hasBin: true peerDependencies: @@ -12599,20 +12730,8 @@ packages: utf-8-validate: optional: true - ws@8.18.3: - resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - - ws@8.20.0: - resolution: {integrity: sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==} + ws@8.20.1: + resolution: {integrity: sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -12675,8 +12794,8 @@ packages: resolution: {integrity: sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==} engines: {node: '>= 6'} - yaml@2.8.3: - resolution: {integrity: sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==} + yaml@2.9.0: + resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==} engines: {node: '>= 14.6'} hasBin: true @@ -12740,149 +12859,138 @@ snapshots: '@adobe/css-tools@4.4.4': {} - '@ai-sdk/gateway@2.0.21(zod@4.3.6)': + '@algolia/abtesting@1.18.1': dependencies: - '@ai-sdk/provider': 2.0.0 - '@ai-sdk/provider-utils': 3.0.19(zod@4.3.6) - '@vercel/oidc': 3.0.5 - zod: 4.3.6 + '@algolia/client-common': 5.52.1 + '@algolia/requester-browser-xhr': 5.52.1 + '@algolia/requester-fetch': 5.52.1 + '@algolia/requester-node-http': 5.52.1 - '@ai-sdk/provider-utils@3.0.19(zod@4.3.6)': + '@algolia/autocomplete-core@1.19.2(@algolia/client-search@5.52.1)(algoliasearch@5.52.1)(search-insights@2.17.3)': dependencies: - '@ai-sdk/provider': 2.0.0 - '@standard-schema/spec': 1.1.0 - eventsource-parser: 3.0.6 - zod: 4.3.6 - - '@ai-sdk/provider@2.0.0': - dependencies: - json-schema: 0.4.0 - - '@ai-sdk/react@2.0.115(react@19.2.5)(zod@4.3.6)': - dependencies: - '@ai-sdk/provider-utils': 3.0.19(zod@4.3.6) - ai: 5.0.113(zod@4.3.6) - react: 19.2.5 - swr: 2.3.8(react@19.2.5) - throttleit: 2.1.0 - optionalDependencies: - zod: 4.3.6 - - '@algolia/abtesting@1.12.0': - dependencies: - '@algolia/client-common': 5.46.0 - '@algolia/requester-browser-xhr': 5.46.0 - '@algolia/requester-fetch': 5.46.0 - '@algolia/requester-node-http': 5.46.0 - - '@algolia/autocomplete-core@1.19.2(@algolia/client-search@5.46.0)(algoliasearch@5.46.0)(search-insights@2.17.3)': - dependencies: - '@algolia/autocomplete-plugin-algolia-insights': 1.19.2(@algolia/client-search@5.46.0)(algoliasearch@5.46.0)(search-insights@2.17.3) - '@algolia/autocomplete-shared': 1.19.2(@algolia/client-search@5.46.0)(algoliasearch@5.46.0) + '@algolia/autocomplete-plugin-algolia-insights': 1.19.2(@algolia/client-search@5.52.1)(algoliasearch@5.52.1)(search-insights@2.17.3) + '@algolia/autocomplete-shared': 1.19.2(@algolia/client-search@5.52.1)(algoliasearch@5.52.1) transitivePeerDependencies: - '@algolia/client-search' - algoliasearch - search-insights - '@algolia/autocomplete-plugin-algolia-insights@1.19.2(@algolia/client-search@5.46.0)(algoliasearch@5.46.0)(search-insights@2.17.3)': + '@algolia/autocomplete-core@1.19.8(@algolia/client-search@5.52.1)(algoliasearch@5.52.1)(search-insights@2.17.3)': dependencies: - '@algolia/autocomplete-shared': 1.19.2(@algolia/client-search@5.46.0)(algoliasearch@5.46.0) + '@algolia/autocomplete-plugin-algolia-insights': 1.19.8(@algolia/client-search@5.52.1)(algoliasearch@5.52.1)(search-insights@2.17.3) + '@algolia/autocomplete-shared': 1.19.8(@algolia/client-search@5.52.1)(algoliasearch@5.52.1) + transitivePeerDependencies: + - '@algolia/client-search' + - algoliasearch + - search-insights + + '@algolia/autocomplete-plugin-algolia-insights@1.19.2(@algolia/client-search@5.52.1)(algoliasearch@5.52.1)(search-insights@2.17.3)': + dependencies: + '@algolia/autocomplete-shared': 1.19.2(@algolia/client-search@5.52.1)(algoliasearch@5.52.1) search-insights: 2.17.3 transitivePeerDependencies: - '@algolia/client-search' - algoliasearch - '@algolia/autocomplete-shared@1.19.2(@algolia/client-search@5.46.0)(algoliasearch@5.46.0)': + '@algolia/autocomplete-plugin-algolia-insights@1.19.8(@algolia/client-search@5.52.1)(algoliasearch@5.52.1)(search-insights@2.17.3)': dependencies: - '@algolia/client-search': 5.46.0 - algoliasearch: 5.46.0 + '@algolia/autocomplete-shared': 1.19.8(@algolia/client-search@5.52.1)(algoliasearch@5.52.1) + search-insights: 2.17.3 + transitivePeerDependencies: + - '@algolia/client-search' + - algoliasearch - '@algolia/client-abtesting@5.46.0': + '@algolia/autocomplete-shared@1.19.2(@algolia/client-search@5.52.1)(algoliasearch@5.52.1)': dependencies: - '@algolia/client-common': 5.46.0 - '@algolia/requester-browser-xhr': 5.46.0 - '@algolia/requester-fetch': 5.46.0 - '@algolia/requester-node-http': 5.46.0 + '@algolia/client-search': 5.52.1 + algoliasearch: 5.52.1 - '@algolia/client-analytics@5.46.0': + '@algolia/autocomplete-shared@1.19.8(@algolia/client-search@5.52.1)(algoliasearch@5.52.1)': dependencies: - '@algolia/client-common': 5.46.0 - '@algolia/requester-browser-xhr': 5.46.0 - '@algolia/requester-fetch': 5.46.0 - '@algolia/requester-node-http': 5.46.0 + '@algolia/client-search': 5.52.1 + algoliasearch: 5.52.1 - '@algolia/client-common@5.46.0': {} - - '@algolia/client-insights@5.46.0': + '@algolia/client-abtesting@5.52.1': dependencies: - '@algolia/client-common': 5.46.0 - '@algolia/requester-browser-xhr': 5.46.0 - '@algolia/requester-fetch': 5.46.0 - '@algolia/requester-node-http': 5.46.0 + '@algolia/client-common': 5.52.1 + '@algolia/requester-browser-xhr': 5.52.1 + '@algolia/requester-fetch': 5.52.1 + '@algolia/requester-node-http': 5.52.1 - '@algolia/client-personalization@5.46.0': + '@algolia/client-analytics@5.52.1': dependencies: - '@algolia/client-common': 5.46.0 - '@algolia/requester-browser-xhr': 5.46.0 - '@algolia/requester-fetch': 5.46.0 - '@algolia/requester-node-http': 5.46.0 + '@algolia/client-common': 5.52.1 + '@algolia/requester-browser-xhr': 5.52.1 + '@algolia/requester-fetch': 5.52.1 + '@algolia/requester-node-http': 5.52.1 - '@algolia/client-query-suggestions@5.46.0': - dependencies: - '@algolia/client-common': 5.46.0 - '@algolia/requester-browser-xhr': 5.46.0 - '@algolia/requester-fetch': 5.46.0 - '@algolia/requester-node-http': 5.46.0 + '@algolia/client-common@5.52.1': {} - '@algolia/client-search@5.46.0': + '@algolia/client-insights@5.52.1': dependencies: - '@algolia/client-common': 5.46.0 - '@algolia/requester-browser-xhr': 5.46.0 - '@algolia/requester-fetch': 5.46.0 - '@algolia/requester-node-http': 5.46.0 + '@algolia/client-common': 5.52.1 + '@algolia/requester-browser-xhr': 5.52.1 + '@algolia/requester-fetch': 5.52.1 + '@algolia/requester-node-http': 5.52.1 + + '@algolia/client-personalization@5.52.1': + dependencies: + '@algolia/client-common': 5.52.1 + '@algolia/requester-browser-xhr': 5.52.1 + '@algolia/requester-fetch': 5.52.1 + '@algolia/requester-node-http': 5.52.1 + + '@algolia/client-query-suggestions@5.52.1': + dependencies: + '@algolia/client-common': 5.52.1 + '@algolia/requester-browser-xhr': 5.52.1 + '@algolia/requester-fetch': 5.52.1 + '@algolia/requester-node-http': 5.52.1 + + '@algolia/client-search@5.52.1': + dependencies: + '@algolia/client-common': 5.52.1 + '@algolia/requester-browser-xhr': 5.52.1 + '@algolia/requester-fetch': 5.52.1 + '@algolia/requester-node-http': 5.52.1 '@algolia/events@4.0.1': {} - '@algolia/ingestion@1.46.0': + '@algolia/ingestion@1.52.1': dependencies: - '@algolia/client-common': 5.46.0 - '@algolia/requester-browser-xhr': 5.46.0 - '@algolia/requester-fetch': 5.46.0 - '@algolia/requester-node-http': 5.46.0 + '@algolia/client-common': 5.52.1 + '@algolia/requester-browser-xhr': 5.52.1 + '@algolia/requester-fetch': 5.52.1 + '@algolia/requester-node-http': 5.52.1 - '@algolia/monitoring@1.46.0': + '@algolia/monitoring@1.52.1': dependencies: - '@algolia/client-common': 5.46.0 - '@algolia/requester-browser-xhr': 5.46.0 - '@algolia/requester-fetch': 5.46.0 - '@algolia/requester-node-http': 5.46.0 + '@algolia/client-common': 5.52.1 + '@algolia/requester-browser-xhr': 5.52.1 + '@algolia/requester-fetch': 5.52.1 + '@algolia/requester-node-http': 5.52.1 - '@algolia/recommend@5.46.0': + '@algolia/recommend@5.52.1': dependencies: - '@algolia/client-common': 5.46.0 - '@algolia/requester-browser-xhr': 5.46.0 - '@algolia/requester-fetch': 5.46.0 - '@algolia/requester-node-http': 5.46.0 + '@algolia/client-common': 5.52.1 + '@algolia/requester-browser-xhr': 5.52.1 + '@algolia/requester-fetch': 5.52.1 + '@algolia/requester-node-http': 5.52.1 - '@algolia/requester-browser-xhr@5.46.0': + '@algolia/requester-browser-xhr@5.52.1': dependencies: - '@algolia/client-common': 5.46.0 + '@algolia/client-common': 5.52.1 - '@algolia/requester-fetch@5.46.0': + '@algolia/requester-fetch@5.52.1': dependencies: - '@algolia/client-common': 5.46.0 + '@algolia/client-common': 5.52.1 - '@algolia/requester-node-http@5.46.0': + '@algolia/requester-node-http@5.52.1': dependencies: - '@algolia/client-common': 5.46.0 + '@algolia/client-common': 5.52.1 '@alloc/quick-lru@5.2.0': {} - '@ampproject/remapping@2.3.0': - dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - '@angular-devkit/core@19.2.24(chokidar@4.0.3)': dependencies: ajv: 8.18.0 @@ -12894,11 +13002,11 @@ snapshots: optionalDependencies: chokidar: 4.0.3 - '@angular-devkit/schematics-cli@19.2.24(@types/node@24.12.2)(chokidar@4.0.3)': + '@angular-devkit/schematics-cli@19.2.24(@types/node@24.12.4)(chokidar@4.0.3)': dependencies: '@angular-devkit/core': 19.2.24(chokidar@4.0.3) '@angular-devkit/schematics': 19.2.24(chokidar@4.0.3) - '@inquirer/prompts': 7.3.2(@types/node@24.12.2) + '@inquirer/prompts': 7.3.2(@types/node@24.12.4) ansi-colors: 4.1.3 symbol-observable: 4.0.0 yargs-parser: 21.1.1 @@ -12919,7 +13027,7 @@ snapshots: '@antfu/install-pkg@1.1.0': dependencies: package-manager-detector: 1.6.0 - tinyexec: 1.1.1 + tinyexec: 1.1.2 '@asamuzakjp/css-color@3.2.0': dependencies: @@ -12936,16 +13044,16 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/compat-data@7.28.5': {} + '@babel/compat-data@7.29.3': {} - '@babel/core@7.28.5': + '@babel/core@7.29.0': dependencies: '@babel/code-frame': 7.29.0 '@babel/generator': 7.29.1 - '@babel/helper-compilation-targets': 7.27.2 - '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5) - '@babel/helpers': 7.28.4 - '@babel/parser': 7.29.2 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helpers': 7.29.2 + '@babel/parser': 7.29.3 '@babel/template': 7.28.6 '@babel/traverse': 7.29.0 '@babel/types': 7.29.0 @@ -12960,7 +13068,7 @@ snapshots: '@babel/generator@7.29.1': dependencies: - '@babel/parser': 7.29.2 + '@babel/parser': 7.29.3 '@babel/types': 7.29.0 '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 @@ -12970,42 +13078,42 @@ snapshots: dependencies: '@babel/types': 7.29.0 - '@babel/helper-compilation-targets@7.27.2': + '@babel/helper-compilation-targets@7.28.6': dependencies: - '@babel/compat-data': 7.28.5 + '@babel/compat-data': 7.29.3 '@babel/helper-validator-option': 7.27.1 browserslist: 4.28.2 lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-create-class-features-plugin@7.28.5(@babel/core@7.28.5)': + '@babel/helper-create-class-features-plugin@7.29.3(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-member-expression-to-functions': 7.28.5 '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.5) + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0) '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 '@babel/traverse': 7.29.0 semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/helper-create-regexp-features-plugin@7.28.5(@babel/core@7.28.5)': + '@babel/helper-create-regexp-features-plugin@7.28.5(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-annotate-as-pure': 7.27.3 regexpu-core: 6.4.0 semver: 6.3.1 - '@babel/helper-define-polyfill-provider@0.6.5(@babel/core@7.28.5)': + '@babel/helper-define-polyfill-provider@0.6.8(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-compilation-targets': 7.27.2 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 debug: 4.4.3 lodash.debounce: 4.0.8 - resolve: 1.22.11 + resolve: 1.22.12 transitivePeerDependencies: - supports-color @@ -13018,17 +13126,17 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helper-module-imports@7.27.1': + '@babel/helper-module-imports@7.28.6': dependencies: '@babel/traverse': 7.29.0 '@babel/types': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.5)': + '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-module-imports': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 '@babel/helper-validator-identifier': 7.28.5 '@babel/traverse': 7.29.0 transitivePeerDependencies: @@ -13038,20 +13146,20 @@ snapshots: dependencies: '@babel/types': 7.29.0 - '@babel/helper-plugin-utils@7.27.1': {} + '@babel/helper-plugin-utils@7.28.6': {} - '@babel/helper-remap-async-to-generator@7.27.1(@babel/core@7.28.5)': + '@babel/helper-remap-async-to-generator@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-wrap-function': 7.28.3 + '@babel/helper-wrap-function': 7.28.6 '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/helper-replace-supers@7.27.1(@babel/core@7.28.5)': + '@babel/helper-replace-supers@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-member-expression-to-functions': 7.28.5 '@babel/helper-optimise-call-expression': 7.27.1 '@babel/traverse': 7.29.0 @@ -13071,7 +13179,7 @@ snapshots: '@babel/helper-validator-option@7.27.1': {} - '@babel/helper-wrap-function@7.28.3': + '@babel/helper-wrap-function@7.28.6': dependencies: '@babel/template': 7.28.6 '@babel/traverse': 7.29.0 @@ -13079,7 +13187,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helpers@7.28.4': + '@babel/helpers@7.29.2': dependencies: '@babel/template': 7.28.6 '@babel/types': 7.29.0 @@ -13088,589 +13196,598 @@ snapshots: dependencies: '@babel/types': 7.29.0 - '@babel/parser@7.29.2': + '@babel/parser@7.29.3': dependencies: '@babel/types': 7.29.0 - '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5(@babel/core@7.28.5)': + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-bugfix-safari-rest-destructuring-rhs-array@7.29.3(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/plugin-transform-optional-chaining': 7.28.5(@babel/core@7.28.5) transitivePeerDependencies: - supports-color - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.3(@babel/core@7.28.5)': + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.29.0) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.28.5)': + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 - '@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.28.5)': + '@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-import-assertions@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-syntax-import-assertions@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-import-attributes@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-syntax-import-attributes@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.28.5)': + '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.5) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-async-generator-functions@7.28.0(@babel/core@7.28.5)': + '@babel/plugin-transform-async-generator-functions@7.29.0(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.28.5) + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.29.0) '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-async-to-generator@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-async-to-generator@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-module-imports': 7.27.1 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.28.5) + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.29.0) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-block-scoped-functions@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-block-scoped-functions@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-block-scoping@7.28.5(@babel/core@7.28.5)': + '@babel/plugin-transform-block-scoping@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-class-properties@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-class-properties@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.5) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-class-static-block@7.28.3(@babel/core@7.28.5)': + '@babel/plugin-transform-class-static-block@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.5) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-classes@7.28.4(@babel/core@7.28.5)': + '@babel/plugin-transform-classes@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-compilation-targets': 7.28.6 '@babel/helper-globals': 7.28.0 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0) '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-computed-properties@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-computed-properties@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 '@babel/template': 7.28.6 - '@babel/plugin-transform-destructuring@7.28.5(@babel/core@7.28.5)': + '@babel/plugin-transform-destructuring@7.28.5(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-dotall-regex@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-dotall-regex@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.5) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-duplicate-keys@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-duplicate-keys@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.0(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.5) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-dynamic-import@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-dynamic-import@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-explicit-resource-management@7.28.0(@babel/core@7.28.5)': + '@babel/plugin-transform-explicit-resource-management@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.28.5) + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.0) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-exponentiation-operator@7.28.5(@babel/core@7.28.5)': + '@babel/plugin-transform-exponentiation-operator@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-export-namespace-from@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-export-namespace-from@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-for-of@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-for-of@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-function-name@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-function-name@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-compilation-targets': 7.27.2 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-json-strings@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-json-strings@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-literals@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-literals@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-logical-assignment-operators@7.28.5(@babel/core@7.28.5)': + '@babel/plugin-transform-logical-assignment-operators@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-member-expression-literals@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-member-expression-literals@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-modules-amd@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-modules-amd@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-commonjs@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-modules-commonjs@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-systemjs@7.28.5(@babel/core@7.28.5)': + '@babel/plugin-transform-modules-systemjs@7.29.4(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-validator-identifier': 7.28.5 '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-umd@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-modules-umd@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-named-capturing-groups-regex@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-named-capturing-groups-regex@7.29.0(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.5) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-new-target@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-new-target@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-nullish-coalescing-operator@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-nullish-coalescing-operator@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-numeric-separator@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-numeric-separator@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-object-rest-spread@7.28.4(@babel/core@7.28.5)': + '@babel/plugin-transform-object-rest-spread@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-compilation-targets': 7.27.2 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.28.5) - '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.28.5) + '@babel/core': 7.29.0 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.0) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.0) '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-object-super@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-object-super@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.5) + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-optional-catch-binding@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-optional-catch-binding@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-optional-chaining@7.28.5(@babel/core@7.28.5)': + '@babel/plugin-transform-optional-chaining@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-parameters@7.27.7(@babel/core@7.28.5)': + '@babel/plugin-transform-parameters@7.27.7(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-private-methods@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-private-methods@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.5) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-private-property-in-object@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-private-property-in-object@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.5) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-property-literals@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-property-literals@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-react-constant-elements@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-react-constant-elements@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-react-display-name@7.28.0(@babel/core@7.28.5)': + '@babel/plugin-transform-react-display-name@7.28.0(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-react-jsx-development@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-react-jsx-development@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.28.5) + '@babel/core': 7.29.0 + '@babel/plugin-transform-react-jsx': 7.28.6(@babel/core@7.29.0) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-react-jsx@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-react-jsx@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-module-imports': 7.27.1 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.5) + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) '@babel/types': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-react-pure-annotations@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-react-pure-annotations@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-regenerator@7.28.4(@babel/core@7.28.5)': + '@babel/plugin-transform-regenerator@7.29.0(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-regexp-modifiers@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-regexp-modifiers@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.5) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-reserved-words@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-reserved-words@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-runtime@7.28.5(@babel/core@7.28.5)': + '@babel/plugin-transform-runtime@7.29.0(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-module-imports': 7.27.1 - '@babel/helper-plugin-utils': 7.27.1 - babel-plugin-polyfill-corejs2: 0.4.14(@babel/core@7.28.5) - babel-plugin-polyfill-corejs3: 0.13.0(@babel/core@7.28.5) - babel-plugin-polyfill-regenerator: 0.6.5(@babel/core@7.28.5) + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + babel-plugin-polyfill-corejs2: 0.4.17(@babel/core@7.29.0) + babel-plugin-polyfill-corejs3: 0.13.0(@babel/core@7.29.0) + babel-plugin-polyfill-regenerator: 0.6.8(@babel/core@7.29.0) semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-shorthand-properties@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-shorthand-properties@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-spread@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-spread@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-sticky-regex@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-sticky-regex@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-template-literals@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-template-literals@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-typeof-symbol@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-typeof-symbol@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-typescript@7.28.5(@babel/core@7.28.5)': + '@babel/plugin-transform-typescript@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.5) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-unicode-escapes@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-unicode-escapes@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-unicode-property-regex@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-unicode-property-regex@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.5) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-unicode-regex@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-unicode-regex@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.5) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-unicode-sets-regex@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-unicode-sets-regex@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.5) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 - '@babel/preset-env@7.28.5(@babel/core@7.28.5)': + '@babel/preset-env@7.29.5(@babel/core@7.29.0)': dependencies: - '@babel/compat-data': 7.28.5 - '@babel/core': 7.28.5 - '@babel/helper-compilation-targets': 7.27.2 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/compat-data': 7.29.3 + '@babel/core': 7.29.0 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-validator-option': 7.27.1 - '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.28.5(@babel/core@7.28.5) - '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.28.3(@babel/core@7.28.5) - '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.28.5) - '@babel/plugin-syntax-import-assertions': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-syntax-import-attributes': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.28.5) - '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-async-generator-functions': 7.28.0(@babel/core@7.28.5) - '@babel/plugin-transform-async-to-generator': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-block-scoped-functions': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-block-scoping': 7.28.5(@babel/core@7.28.5) - '@babel/plugin-transform-class-properties': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-class-static-block': 7.28.3(@babel/core@7.28.5) - '@babel/plugin-transform-classes': 7.28.4(@babel/core@7.28.5) - '@babel/plugin-transform-computed-properties': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.28.5) - '@babel/plugin-transform-dotall-regex': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-duplicate-keys': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-dynamic-import': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-explicit-resource-management': 7.28.0(@babel/core@7.28.5) - '@babel/plugin-transform-exponentiation-operator': 7.28.5(@babel/core@7.28.5) - '@babel/plugin-transform-export-namespace-from': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-for-of': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-function-name': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-json-strings': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-literals': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-logical-assignment-operators': 7.28.5(@babel/core@7.28.5) - '@babel/plugin-transform-member-expression-literals': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-modules-amd': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-modules-systemjs': 7.28.5(@babel/core@7.28.5) - '@babel/plugin-transform-modules-umd': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-named-capturing-groups-regex': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-new-target': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-nullish-coalescing-operator': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-numeric-separator': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-object-rest-spread': 7.28.4(@babel/core@7.28.5) - '@babel/plugin-transform-object-super': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-optional-catch-binding': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-optional-chaining': 7.28.5(@babel/core@7.28.5) - '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.28.5) - '@babel/plugin-transform-private-methods': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-private-property-in-object': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-property-literals': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-regenerator': 7.28.4(@babel/core@7.28.5) - '@babel/plugin-transform-regexp-modifiers': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-reserved-words': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-spread': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-sticky-regex': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-template-literals': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-typeof-symbol': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-unicode-escapes': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-unicode-property-regex': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-unicode-sets-regex': 7.27.1(@babel/core@7.28.5) - '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.28.5) - babel-plugin-polyfill-corejs2: 0.4.14(@babel/core@7.28.5) - babel-plugin-polyfill-corejs3: 0.13.0(@babel/core@7.28.5) - babel-plugin-polyfill-regenerator: 0.6.5(@babel/core@7.28.5) + '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.28.5(@babel/core@7.29.0) + '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-bugfix-safari-rest-destructuring-rhs-array': 7.29.3(@babel/core@7.29.0) + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.0) + '@babel/plugin-syntax-import-assertions': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.29.0) + '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-async-generator-functions': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-transform-async-to-generator': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-block-scoped-functions': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-block-scoping': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-class-properties': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-class-static-block': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-classes': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-computed-properties': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.0) + '@babel/plugin-transform-dotall-regex': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-duplicate-keys': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-transform-dynamic-import': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-explicit-resource-management': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-exponentiation-operator': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-export-namespace-from': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-for-of': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-function-name': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-json-strings': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-literals': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-logical-assignment-operators': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-member-expression-literals': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-modules-amd': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-modules-systemjs': 7.29.4(@babel/core@7.29.0) + '@babel/plugin-transform-modules-umd': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-named-capturing-groups-regex': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-transform-new-target': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-nullish-coalescing-operator': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-numeric-separator': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-object-rest-spread': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-object-super': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-optional-catch-binding': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.0) + '@babel/plugin-transform-private-methods': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-private-property-in-object': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-property-literals': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-regenerator': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-transform-regexp-modifiers': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-reserved-words': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-spread': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-sticky-regex': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-template-literals': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-typeof-symbol': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-unicode-escapes': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-unicode-property-regex': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-unicode-sets-regex': 7.28.6(@babel/core@7.29.0) + '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.29.0) + babel-plugin-polyfill-corejs2: 0.4.17(@babel/core@7.29.0) + babel-plugin-polyfill-corejs3: 0.14.2(@babel/core@7.29.0) + babel-plugin-polyfill-regenerator: 0.6.8(@babel/core@7.29.0) core-js-compat: 3.49.0 semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.28.5)': + '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 '@babel/types': 7.29.0 esutils: 2.0.3 - '@babel/preset-react@7.28.5(@babel/core@7.28.5)': + '@babel/preset-react@7.28.5(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-validator-option': 7.27.1 - '@babel/plugin-transform-react-display-name': 7.28.0(@babel/core@7.28.5) - '@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-react-jsx-development': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-react-pure-annotations': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-react-display-name': 7.28.0(@babel/core@7.29.0) + '@babel/plugin-transform-react-jsx': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-react-jsx-development': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-react-pure-annotations': 7.27.1(@babel/core@7.29.0) transitivePeerDependencies: - supports-color - '@babel/preset-typescript@7.28.5(@babel/core@7.28.5)': + '@babel/preset-typescript@7.28.5(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-validator-option': 7.27.1 - '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-typescript': 7.28.5(@babel/core@7.28.5) + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.29.0) transitivePeerDependencies: - supports-color - '@babel/runtime@7.29.2': {} + '@babel/runtime@7.29.7': {} '@babel/template@7.28.6': dependencies: '@babel/code-frame': 7.29.0 - '@babel/parser': 7.29.2 + '@babel/parser': 7.29.3 '@babel/types': 7.29.0 '@babel/traverse@7.27.0': dependencies: '@babel/code-frame': 7.29.0 '@babel/generator': 7.29.1 - '@babel/parser': 7.29.2 + '@babel/parser': 7.29.3 '@babel/template': 7.28.6 '@babel/types': 7.29.0 debug: 4.4.3 @@ -13683,7 +13800,7 @@ snapshots: '@babel/code-frame': 7.29.0 '@babel/generator': 7.29.1 '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.29.2 + '@babel/parser': 7.29.3 '@babel/template': 7.28.6 '@babel/types': 7.29.0 debug: 4.4.3 @@ -13701,37 +13818,22 @@ snapshots: '@borewit/text-codec@0.2.2': {} - '@braintree/sanitize-url@7.1.1': {} + '@braintree/sanitize-url@7.1.2': {} - '@chevrotain/cst-dts-gen@11.0.3': - dependencies: - '@chevrotain/gast': 11.0.3 - '@chevrotain/types': 11.0.3 - lodash-es: 4.17.21 + '@chevrotain/types@11.1.2': {} - '@chevrotain/gast@11.0.3': - dependencies: - '@chevrotain/types': 11.0.3 - lodash-es: 4.17.21 - - '@chevrotain/regexp-to-ast@11.0.3': {} - - '@chevrotain/types@11.0.3': {} - - '@chevrotain/utils@11.0.3': {} - - '@codemirror/autocomplete@6.20.1': + '@codemirror/autocomplete@6.20.2': dependencies: '@codemirror/language': 6.12.3 '@codemirror/state': 6.6.0 - '@codemirror/view': 6.41.1 + '@codemirror/view': 6.43.0 '@lezer/common': 1.5.2 '@codemirror/commands@6.10.3': dependencies: '@codemirror/language': 6.12.3 '@codemirror/state': 6.6.0 - '@codemirror/view': 6.41.1 + '@codemirror/view': 6.43.0 '@lezer/common': 1.5.2 '@codemirror/lang-json@6.0.2': @@ -13742,29 +13844,29 @@ snapshots: '@codemirror/language@6.12.3': dependencies: '@codemirror/state': 6.6.0 - '@codemirror/view': 6.41.1 + '@codemirror/view': 6.43.0 '@lezer/common': 1.5.2 '@lezer/highlight': 1.2.3 '@lezer/lr': 1.4.10 style-mod: 4.1.3 - '@codemirror/lint@6.9.5': + '@codemirror/lint@6.9.6': dependencies: '@codemirror/state': 6.6.0 - '@codemirror/view': 6.41.1 + '@codemirror/view': 6.43.0 crelt: 1.0.6 '@codemirror/search@6.7.0': dependencies: '@codemirror/state': 6.6.0 - '@codemirror/view': 6.41.1 + '@codemirror/view': 6.43.0 crelt: 1.0.6 '@codemirror/state@6.6.0': dependencies: '@marijn/find-cluster-break': 1.0.2 - '@codemirror/view@6.41.1': + '@codemirror/view@6.43.0': dependencies: '@codemirror/state': 6.6.0 crelt: 1.0.6 @@ -13804,261 +13906,272 @@ snapshots: '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-alpha-function@1.0.1(postcss@8.5.12)': + '@csstools/postcss-alpha-function@1.0.1(postcss@8.5.15)': dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.12) - '@csstools/utilities': 2.0.0(postcss@8.5.12) - postcss: 8.5.12 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.15) + '@csstools/utilities': 2.0.0(postcss@8.5.15) + postcss: 8.5.15 - '@csstools/postcss-cascade-layers@5.0.2(postcss@8.5.12)': + '@csstools/postcss-cascade-layers@5.0.2(postcss@8.5.15)': dependencies: '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.1) - postcss: 8.5.12 + postcss: 8.5.15 postcss-selector-parser: 7.1.1 - '@csstools/postcss-color-function-display-p3-linear@1.0.1(postcss@8.5.12)': + '@csstools/postcss-color-function-display-p3-linear@1.0.1(postcss@8.5.15)': dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.12) - '@csstools/utilities': 2.0.0(postcss@8.5.12) - postcss: 8.5.12 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.15) + '@csstools/utilities': 2.0.0(postcss@8.5.15) + postcss: 8.5.15 - '@csstools/postcss-color-function@4.0.12(postcss@8.5.12)': + '@csstools/postcss-color-function@4.0.12(postcss@8.5.15)': dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.12) - '@csstools/utilities': 2.0.0(postcss@8.5.12) - postcss: 8.5.12 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.15) + '@csstools/utilities': 2.0.0(postcss@8.5.15) + postcss: 8.5.15 - '@csstools/postcss-color-mix-function@3.0.12(postcss@8.5.12)': + '@csstools/postcss-color-mix-function@3.0.12(postcss@8.5.15)': dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.12) - '@csstools/utilities': 2.0.0(postcss@8.5.12) - postcss: 8.5.12 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.15) + '@csstools/utilities': 2.0.0(postcss@8.5.15) + postcss: 8.5.15 - '@csstools/postcss-color-mix-variadic-function-arguments@1.0.2(postcss@8.5.12)': + '@csstools/postcss-color-mix-variadic-function-arguments@1.0.2(postcss@8.5.15)': dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.12) - '@csstools/utilities': 2.0.0(postcss@8.5.12) - postcss: 8.5.12 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.15) + '@csstools/utilities': 2.0.0(postcss@8.5.15) + postcss: 8.5.15 - '@csstools/postcss-content-alt-text@2.0.8(postcss@8.5.12)': + '@csstools/postcss-content-alt-text@2.0.8(postcss@8.5.15)': dependencies: '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.12) - '@csstools/utilities': 2.0.0(postcss@8.5.12) - postcss: 8.5.12 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.15) + '@csstools/utilities': 2.0.0(postcss@8.5.15) + postcss: 8.5.15 - '@csstools/postcss-contrast-color-function@2.0.12(postcss@8.5.12)': + '@csstools/postcss-contrast-color-function@2.0.12(postcss@8.5.15)': dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.12) - '@csstools/utilities': 2.0.0(postcss@8.5.12) - postcss: 8.5.12 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.15) + '@csstools/utilities': 2.0.0(postcss@8.5.15) + postcss: 8.5.15 - '@csstools/postcss-exponential-functions@2.0.9(postcss@8.5.12)': + '@csstools/postcss-exponential-functions@2.0.9(postcss@8.5.15)': dependencies: '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - postcss: 8.5.12 + postcss: 8.5.15 - '@csstools/postcss-font-format-keywords@4.0.0(postcss@8.5.12)': + '@csstools/postcss-font-format-keywords@4.0.0(postcss@8.5.15)': dependencies: - '@csstools/utilities': 2.0.0(postcss@8.5.12) - postcss: 8.5.12 + '@csstools/utilities': 2.0.0(postcss@8.5.15) + postcss: 8.5.15 postcss-value-parser: 4.2.0 - '@csstools/postcss-gamut-mapping@2.0.11(postcss@8.5.12)': + '@csstools/postcss-gamut-mapping@2.0.11(postcss@8.5.15)': dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - postcss: 8.5.12 + postcss: 8.5.15 - '@csstools/postcss-gradients-interpolation-method@5.0.12(postcss@8.5.12)': + '@csstools/postcss-gradients-interpolation-method@5.0.12(postcss@8.5.15)': dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.12) - '@csstools/utilities': 2.0.0(postcss@8.5.12) - postcss: 8.5.12 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.15) + '@csstools/utilities': 2.0.0(postcss@8.5.15) + postcss: 8.5.15 - '@csstools/postcss-hwb-function@4.0.12(postcss@8.5.12)': + '@csstools/postcss-hwb-function@4.0.12(postcss@8.5.15)': dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.12) - '@csstools/utilities': 2.0.0(postcss@8.5.12) - postcss: 8.5.12 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.15) + '@csstools/utilities': 2.0.0(postcss@8.5.15) + postcss: 8.5.15 - '@csstools/postcss-ic-unit@4.0.4(postcss@8.5.12)': + '@csstools/postcss-ic-unit@4.0.4(postcss@8.5.15)': dependencies: - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.12) - '@csstools/utilities': 2.0.0(postcss@8.5.12) - postcss: 8.5.12 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.15) + '@csstools/utilities': 2.0.0(postcss@8.5.15) + postcss: 8.5.15 postcss-value-parser: 4.2.0 - '@csstools/postcss-initial@2.0.1(postcss@8.5.12)': + '@csstools/postcss-initial@2.0.1(postcss@8.5.15)': dependencies: - postcss: 8.5.12 + postcss: 8.5.15 - '@csstools/postcss-is-pseudo-class@5.0.3(postcss@8.5.12)': + '@csstools/postcss-is-pseudo-class@5.0.3(postcss@8.5.15)': dependencies: '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.1) - postcss: 8.5.12 + postcss: 8.5.15 postcss-selector-parser: 7.1.1 - '@csstools/postcss-light-dark-function@2.0.11(postcss@8.5.12)': + '@csstools/postcss-light-dark-function@2.0.11(postcss@8.5.15)': dependencies: '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.12) - '@csstools/utilities': 2.0.0(postcss@8.5.12) - postcss: 8.5.12 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.15) + '@csstools/utilities': 2.0.0(postcss@8.5.15) + postcss: 8.5.15 - '@csstools/postcss-logical-float-and-clear@3.0.0(postcss@8.5.12)': + '@csstools/postcss-logical-float-and-clear@3.0.0(postcss@8.5.15)': dependencies: - postcss: 8.5.12 + postcss: 8.5.15 - '@csstools/postcss-logical-overflow@2.0.0(postcss@8.5.12)': + '@csstools/postcss-logical-overflow@2.0.0(postcss@8.5.15)': dependencies: - postcss: 8.5.12 + postcss: 8.5.15 - '@csstools/postcss-logical-overscroll-behavior@2.0.0(postcss@8.5.12)': + '@csstools/postcss-logical-overscroll-behavior@2.0.0(postcss@8.5.15)': dependencies: - postcss: 8.5.12 + postcss: 8.5.15 - '@csstools/postcss-logical-resize@3.0.0(postcss@8.5.12)': + '@csstools/postcss-logical-resize@3.0.0(postcss@8.5.15)': dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-value-parser: 4.2.0 - '@csstools/postcss-logical-viewport-units@3.0.4(postcss@8.5.12)': + '@csstools/postcss-logical-viewport-units@3.0.4(postcss@8.5.15)': dependencies: '@csstools/css-tokenizer': 3.0.4 - '@csstools/utilities': 2.0.0(postcss@8.5.12) - postcss: 8.5.12 + '@csstools/utilities': 2.0.0(postcss@8.5.15) + postcss: 8.5.15 - '@csstools/postcss-media-minmax@2.0.9(postcss@8.5.12)': + '@csstools/postcss-media-minmax@2.0.9(postcss@8.5.15)': dependencies: '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 '@csstools/media-query-list-parser': 4.0.3(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) - postcss: 8.5.12 + postcss: 8.5.15 - '@csstools/postcss-media-queries-aspect-ratio-number-values@3.0.5(postcss@8.5.12)': + '@csstools/postcss-media-queries-aspect-ratio-number-values@3.0.5(postcss@8.5.15)': dependencies: '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 '@csstools/media-query-list-parser': 4.0.3(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) - postcss: 8.5.12 + postcss: 8.5.15 - '@csstools/postcss-nested-calc@4.0.0(postcss@8.5.12)': + '@csstools/postcss-nested-calc@4.0.0(postcss@8.5.15)': dependencies: - '@csstools/utilities': 2.0.0(postcss@8.5.12) - postcss: 8.5.12 + '@csstools/utilities': 2.0.0(postcss@8.5.15) + postcss: 8.5.15 postcss-value-parser: 4.2.0 - '@csstools/postcss-normalize-display-values@4.0.0(postcss@8.5.12)': + '@csstools/postcss-normalize-display-values@4.0.1(postcss@8.5.15)': dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-value-parser: 4.2.0 - '@csstools/postcss-oklab-function@4.0.12(postcss@8.5.12)': + '@csstools/postcss-oklab-function@4.0.12(postcss@8.5.15)': dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.12) - '@csstools/utilities': 2.0.0(postcss@8.5.12) - postcss: 8.5.12 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.15) + '@csstools/utilities': 2.0.0(postcss@8.5.15) + postcss: 8.5.15 - '@csstools/postcss-position-area-property@1.0.0(postcss@8.5.12)': + '@csstools/postcss-position-area-property@1.0.0(postcss@8.5.15)': dependencies: - postcss: 8.5.12 + postcss: 8.5.15 - '@csstools/postcss-progressive-custom-properties@4.2.1(postcss@8.5.12)': + '@csstools/postcss-progressive-custom-properties@4.2.1(postcss@8.5.15)': dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-value-parser: 4.2.0 - '@csstools/postcss-random-function@2.0.1(postcss@8.5.12)': + '@csstools/postcss-property-rule-prelude-list@1.0.0(postcss@8.5.15)': + dependencies: + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + postcss: 8.5.15 + + '@csstools/postcss-random-function@2.0.1(postcss@8.5.15)': dependencies: '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - postcss: 8.5.12 + postcss: 8.5.15 - '@csstools/postcss-relative-color-syntax@3.0.12(postcss@8.5.12)': + '@csstools/postcss-relative-color-syntax@3.0.12(postcss@8.5.15)': dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.12) - '@csstools/utilities': 2.0.0(postcss@8.5.12) - postcss: 8.5.12 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.15) + '@csstools/utilities': 2.0.0(postcss@8.5.15) + postcss: 8.5.15 - '@csstools/postcss-scope-pseudo-class@4.0.1(postcss@8.5.12)': + '@csstools/postcss-scope-pseudo-class@4.0.1(postcss@8.5.15)': dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-selector-parser: 7.1.1 - '@csstools/postcss-sign-functions@1.1.4(postcss@8.5.12)': + '@csstools/postcss-sign-functions@1.1.4(postcss@8.5.15)': dependencies: '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - postcss: 8.5.12 + postcss: 8.5.15 - '@csstools/postcss-stepped-value-functions@4.0.9(postcss@8.5.12)': + '@csstools/postcss-stepped-value-functions@4.0.9(postcss@8.5.15)': dependencies: '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - postcss: 8.5.12 + postcss: 8.5.15 - '@csstools/postcss-system-ui-font-family@1.0.0(postcss@8.5.12)': + '@csstools/postcss-syntax-descriptor-syntax-production@1.0.1(postcss@8.5.15)': + dependencies: + '@csstools/css-tokenizer': 3.0.4 + postcss: 8.5.15 + + '@csstools/postcss-system-ui-font-family@1.0.0(postcss@8.5.15)': dependencies: '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - postcss: 8.5.12 + postcss: 8.5.15 - '@csstools/postcss-text-decoration-shorthand@4.0.3(postcss@8.5.12)': + '@csstools/postcss-text-decoration-shorthand@4.0.3(postcss@8.5.15)': dependencies: '@csstools/color-helpers': 5.1.0 - postcss: 8.5.12 + postcss: 8.5.15 postcss-value-parser: 4.2.0 - '@csstools/postcss-trigonometric-functions@4.0.9(postcss@8.5.12)': + '@csstools/postcss-trigonometric-functions@4.0.9(postcss@8.5.15)': dependencies: '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - postcss: 8.5.12 + postcss: 8.5.15 - '@csstools/postcss-unset-value@4.0.0(postcss@8.5.12)': + '@csstools/postcss-unset-value@4.0.0(postcss@8.5.15)': dependencies: - postcss: 8.5.12 + postcss: 8.5.15 '@csstools/selector-resolve-nested@3.1.0(postcss-selector-parser@7.1.1)': dependencies: @@ -14068,94 +14181,135 @@ snapshots: dependencies: postcss-selector-parser: 7.1.1 - '@csstools/utilities@2.0.0(postcss@8.5.12)': + '@csstools/utilities@2.0.0(postcss@8.5.15)': dependencies: - postcss: 8.5.12 + postcss: 8.5.15 '@discoveryjs/json-ext@0.5.7': {} - '@docsearch/core@4.3.1(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@docsearch/core@4.6.3(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': optionalDependencies: - '@types/react': 19.2.14 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + '@types/react': 19.2.15 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) - '@docsearch/css@4.3.2': {} + '@docsearch/css@4.6.3': {} - '@docsearch/react@4.3.2(@algolia/client-search@5.46.0)(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(search-insights@2.17.3)': + '@docsearch/react@4.6.3(@algolia/client-search@5.52.1)(@types/react@19.2.15)(algoliasearch@5.52.1)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(search-insights@2.17.3)': dependencies: - '@ai-sdk/react': 2.0.115(react@19.2.5)(zod@4.3.6) - '@algolia/autocomplete-core': 1.19.2(@algolia/client-search@5.46.0)(algoliasearch@5.46.0)(search-insights@2.17.3) - '@docsearch/core': 4.3.1(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docsearch/css': 4.3.2 - ai: 5.0.113(zod@4.3.6) - algoliasearch: 5.46.0 - marked: 16.4.2 - zod: 4.3.6 + '@algolia/autocomplete-core': 1.19.2(@algolia/client-search@5.52.1)(algoliasearch@5.52.1)(search-insights@2.17.3) + '@docsearch/core': 4.6.3(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docsearch/css': 4.6.3 optionalDependencies: - '@types/react': 19.2.14 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + '@types/react': 19.2.15 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) search-insights: 2.17.3 transitivePeerDependencies: - '@algolia/client-search' + - algoliasearch - '@docusaurus/babel@3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@docusaurus/babel@3.10.1(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/generator': 7.29.1 - '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.28.5) - '@babel/plugin-transform-runtime': 7.28.5(@babel/core@7.28.5) - '@babel/preset-env': 7.28.5(@babel/core@7.28.5) - '@babel/preset-react': 7.28.5(@babel/core@7.28.5) - '@babel/preset-typescript': 7.28.5(@babel/core@7.28.5) - '@babel/runtime': 7.29.2 + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-transform-runtime': 7.29.0(@babel/core@7.29.0) + '@babel/preset-env': 7.29.5(@babel/core@7.29.0) + '@babel/preset-react': 7.28.5(@babel/core@7.29.0) + '@babel/preset-typescript': 7.28.5(@babel/core@7.29.0) + '@babel/runtime': 7.29.7 '@babel/traverse': 7.29.0 - '@docusaurus/logger': 3.10.0 - '@docusaurus/utils': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/logger': 3.10.1 + '@docusaurus/utils': 3.10.1(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) babel-plugin-dynamic-import-node: 2.3.3 - fs-extra: 11.3.4 + fs-extra: 11.3.5 tslib: 2.8.1 transitivePeerDependencies: + - '@minify-html/node' - '@swc/core' + - '@swc/css' + - '@swc/html' + - clean-css + - cssnano + - csso - esbuild + - html-minifier-terser + - lightningcss + - postcss - react - react-dom - supports-color - uglify-js - webpack-cli - '@docusaurus/bundler@3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3)': + '@docusaurus/babel@3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@babel/core': 7.28.5 - '@docusaurus/babel': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/cssnano-preset': 3.10.0 - '@docusaurus/logger': 3.10.0 - '@docusaurus/types': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - babel-loader: 9.2.1(@babel/core@7.28.5)(webpack@5.106.2) - clean-css: 5.3.3 - copy-webpack-plugin: 11.0.0(webpack@5.106.2) - css-loader: 6.11.0(webpack@5.106.2) - css-minimizer-webpack-plugin: 5.0.1(clean-css@5.3.3)(webpack@5.106.2) - cssnano: 6.1.2(postcss@8.5.12) - file-loader: 6.2.0(webpack@5.106.2) - html-minifier-terser: 7.2.0 - mini-css-extract-plugin: 2.9.4(webpack@5.106.2) - null-loader: 4.0.1(webpack@5.106.2) - postcss: 8.5.12 - postcss-loader: 7.3.4(postcss@8.5.12)(typescript@6.0.3)(webpack@5.106.2) - postcss-preset-env: 10.5.0(postcss@8.5.12) - terser-webpack-plugin: 5.4.0(webpack@5.106.2) + '@babel/core': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-transform-runtime': 7.29.0(@babel/core@7.29.0) + '@babel/preset-env': 7.29.5(@babel/core@7.29.0) + '@babel/preset-react': 7.28.5(@babel/core@7.29.0) + '@babel/preset-typescript': 7.28.5(@babel/core@7.29.0) + '@babel/runtime': 7.29.7 + '@babel/traverse': 7.29.0 + '@docusaurus/logger': 3.10.1 + '@docusaurus/utils': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + babel-plugin-dynamic-import-node: 2.3.3 + fs-extra: 11.3.5 tslib: 2.8.1 - url-loader: 4.1.1(file-loader@6.2.0(webpack@5.106.2))(webpack@5.106.2) - webpack: 5.106.2 - webpackbar: 7.0.0(webpack@5.106.2) transitivePeerDependencies: + - '@minify-html/node' + - '@swc/core' + - '@swc/css' + - '@swc/html' + - clean-css + - cssnano + - csso + - esbuild + - html-minifier-terser + - lightningcss + - postcss + - react + - react-dom + - supports-color + - uglify-js + - webpack-cli + + '@docusaurus/bundler@3.10.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3)': + dependencies: + '@babel/core': 7.29.0 + '@docusaurus/babel': 3.10.1(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/cssnano-preset': 3.10.1 + '@docusaurus/logger': 3.10.1 + '@docusaurus/types': 3.10.1(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils': 3.10.1(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + babel-loader: 9.2.1(@babel/core@7.29.0)(webpack@5.107.0(postcss@8.5.15)) + clean-css: 5.3.3 + copy-webpack-plugin: 11.0.0(webpack@5.107.0(postcss@8.5.15)) + css-loader: 6.11.0(webpack@5.107.0(postcss@8.5.15)) + css-minimizer-webpack-plugin: 5.0.1(clean-css@5.3.3)(webpack@5.107.0(postcss@8.5.15)) + cssnano: 6.1.2(postcss@8.5.15) + file-loader: 6.2.0(webpack@5.107.0(postcss@8.5.15)) + html-minifier-terser: 7.2.0 + mini-css-extract-plugin: 2.10.2(webpack@5.107.0(postcss@8.5.15)) + null-loader: 4.0.1(webpack@5.107.0(postcss@8.5.15)) + postcss: 8.5.15 + postcss-loader: 7.3.4(postcss@8.5.15)(typescript@6.0.3)(webpack@5.107.0(postcss@8.5.15)) + postcss-preset-env: 10.6.1(postcss@8.5.15) + terser-webpack-plugin: 5.6.0(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(html-minifier-terser@7.2.0)(postcss@8.5.15)(webpack@5.107.0(postcss@8.5.15)) + tslib: 2.8.1 + url-loader: 4.1.1(file-loader@6.2.0(webpack@5.107.0(postcss@8.5.15)))(webpack@5.107.0(postcss@8.5.15)) + webpack: 5.107.0(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(html-minifier-terser@7.2.0)(postcss@8.5.15) + webpackbar: 7.0.0(webpack@5.107.0(postcss@8.5.15)) + transitivePeerDependencies: + - '@minify-html/node' - '@parcel/css' - '@rspack/core' - '@swc/core' - '@swc/css' + - '@swc/html' - csso - esbuild - lightningcss @@ -14166,97 +14320,103 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/core@3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3)': + '@docusaurus/core@3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3)': dependencies: - '@docusaurus/babel': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/bundler': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/logger': 3.10.0 - '@docusaurus/mdx-loader': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils-common': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils-validation': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@mdx-js/react': 3.1.1(@types/react@19.2.14)(react@19.2.5) + '@docusaurus/babel': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/bundler': 3.10.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/logger': 3.10.1 + '@docusaurus/mdx-loader': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils-common': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils-validation': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@mdx-js/react': 3.1.1(@types/react@19.2.15)(react@19.2.6) boxen: 6.2.1 chalk: 4.1.2 chokidar: 3.6.0 cli-table3: 0.6.5 combine-promises: 1.2.0 commander: 5.1.0 - core-js: 3.47.0 + core-js: 3.49.0 detect-port: 1.6.1 escape-html: 1.0.3 eta: 2.2.0 eval: 0.1.8 execa: 5.1.1 - fs-extra: 11.3.4 + fs-extra: 11.3.5 html-tags: 3.3.1 - html-webpack-plugin: 5.6.5(webpack@5.106.2) + html-webpack-plugin: 5.6.7(webpack@5.107.0(postcss@8.5.15)) leven: 3.1.0 lodash: 4.18.1 open: 8.4.2 p-map: 4.0.0 prompts: 2.4.2 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - react-helmet-async: '@slorber/react-helmet-async@1.3.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)' - react-loadable: '@docusaurus/react-loadable@6.0.0(react@19.2.5)' - react-loadable-ssr-addon-v5-slorber: 1.0.3(@docusaurus/react-loadable@6.0.0(react@19.2.5))(webpack@5.106.2) - react-router: 5.3.4(react@19.2.5) - react-router-config: 5.1.1(react-router@5.3.4(react@19.2.5))(react@19.2.5) - react-router-dom: 5.3.4(react@19.2.5) - semver: 7.7.4 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + react-helmet-async: '@slorber/react-helmet-async@1.3.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)' + react-loadable: '@docusaurus/react-loadable@6.0.0(react@19.2.6)' + react-loadable-ssr-addon-v5-slorber: 1.0.3(@docusaurus/react-loadable@6.0.0(react@19.2.6))(webpack@5.107.0(postcss@8.5.15)) + react-router: 5.3.4(react@19.2.6) + react-router-config: 5.1.1(react-router@5.3.4(react@19.2.6))(react@19.2.6) + react-router-dom: 5.3.4(react@19.2.6) + semver: 7.8.1 serve-handler: 6.1.7 tinypool: 1.1.1 tslib: 2.8.1 update-notifier: 6.0.2 - webpack: 5.106.2 + webpack: 5.107.0(postcss@8.5.15) webpack-bundle-analyzer: 4.10.2 - webpack-dev-server: 5.2.2(tslib@2.8.1)(webpack@5.106.2) + webpack-dev-server: 5.2.4(tslib@2.8.1)(webpack@5.107.0(postcss@8.5.15)) webpack-merge: 6.0.1 transitivePeerDependencies: + - '@minify-html/node' - '@parcel/css' - '@rspack/core' - '@swc/core' - '@swc/css' + - '@swc/html' - bufferutil + - clean-css + - cssnano - csso - debug - esbuild + - html-minifier-terser - lightningcss + - postcss - supports-color - typescript - uglify-js - utf-8-validate - webpack-cli - '@docusaurus/cssnano-preset@3.10.0': + '@docusaurus/cssnano-preset@3.10.1': dependencies: - cssnano-preset-advanced: 6.1.2(postcss@8.5.12) - postcss: 8.5.12 - postcss-sort-media-queries: 5.2.0(postcss@8.5.12) + cssnano-preset-advanced: 6.1.2(postcss@8.5.15) + postcss: 8.5.15 + postcss-sort-media-queries: 5.2.0(postcss@8.5.15) tslib: 2.8.1 - '@docusaurus/logger@3.10.0': + '@docusaurus/logger@3.10.1': dependencies: chalk: 4.1.2 tslib: 2.8.1 - '@docusaurus/mdx-loader@3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@docusaurus/mdx-loader@3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@docusaurus/logger': 3.10.0 - '@docusaurus/utils': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils-validation': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/logger': 3.10.1 + '@docusaurus/utils': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils-validation': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@mdx-js/mdx': 3.1.1 '@slorber/remark-comment': 1.0.0 escape-html: 1.0.3 estree-util-value-to-estree: 3.5.0 - file-loader: 6.2.0(webpack@5.106.2) - fs-extra: 11.3.4 + file-loader: 6.2.0(webpack@5.107.0(postcss@8.5.15)) + fs-extra: 11.3.5 image-size: 2.0.2 mdast-util-mdx: 3.0.0 mdast-util-to-string: 4.0.0 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) rehype-raw: 7.0.0 remark-directive: 3.0.1 remark-emoji: 4.0.1 @@ -14265,166 +14425,208 @@ snapshots: stringify-object: 3.3.0 tslib: 2.8.1 unified: 11.0.5 - unist-util-visit: 5.0.0 - url-loader: 4.1.1(file-loader@6.2.0(webpack@5.106.2))(webpack@5.106.2) + unist-util-visit: 5.1.0 + url-loader: 4.1.1(file-loader@6.2.0(webpack@5.107.0(postcss@8.5.15)))(webpack@5.107.0(postcss@8.5.15)) vfile: 6.0.3 - webpack: 5.106.2 + webpack: 5.107.0(postcss@8.5.15) transitivePeerDependencies: + - '@minify-html/node' - '@swc/core' + - '@swc/css' + - '@swc/html' + - clean-css + - cssnano + - csso - esbuild + - html-minifier-terser + - lightningcss + - postcss - supports-color - uglify-js - webpack-cli - '@docusaurus/module-type-aliases@3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@docusaurus/module-type-aliases@3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@docusaurus/types': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/types': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@types/history': 4.7.11 - '@types/react': 19.2.14 + '@types/react': 19.2.15 '@types/react-router-config': 5.0.11 '@types/react-router-dom': 5.3.3 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - react-helmet-async: '@slorber/react-helmet-async@1.3.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)' - react-loadable: '@docusaurus/react-loadable@6.0.0(react@19.2.5)' + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + react-helmet-async: '@slorber/react-helmet-async@1.3.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)' + react-loadable: '@docusaurus/react-loadable@6.0.0(react@19.2.6)' transitivePeerDependencies: + - '@minify-html/node' - '@swc/core' + - '@swc/css' + - '@swc/html' + - clean-css + - cssnano + - csso - esbuild + - html-minifier-terser + - lightningcss + - postcss - supports-color - uglify-js - webpack-cli - '@docusaurus/plugin-content-blog@3.10.0(@docusaurus/plugin-content-docs@3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3))(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3)': + '@docusaurus/plugin-content-blog@3.10.1(@docusaurus/plugin-content-docs@3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3))(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3)': dependencies: - '@docusaurus/core': 3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/logger': 3.10.0 - '@docusaurus/mdx-loader': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/plugin-content-docs': 3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/theme-common': 3.10.0(@docusaurus/plugin-content-docs@3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3))(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/types': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils-common': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils-validation': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/core': 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/logger': 3.10.1 + '@docusaurus/mdx-loader': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/plugin-content-docs': 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/theme-common': 3.10.1(@docusaurus/plugin-content-docs@3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/types': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils-common': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils-validation': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) cheerio: 1.0.0-rc.12 combine-promises: 1.2.0 feed: 4.2.2 - fs-extra: 11.3.4 + fs-extra: 11.3.5 lodash: 4.18.1 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) schema-dts: 1.1.5 srcset: 4.0.0 tslib: 2.8.1 - unist-util-visit: 5.0.0 + unist-util-visit: 5.1.0 utility-types: 3.11.0 - webpack: 5.106.2 + webpack: 5.107.0(postcss@8.5.15) transitivePeerDependencies: - '@docusaurus/faster' - '@mdx-js/react' + - '@minify-html/node' - '@parcel/css' - '@rspack/core' - '@swc/core' - '@swc/css' + - '@swc/html' - bufferutil + - clean-css + - cssnano - csso - debug - esbuild + - html-minifier-terser - lightningcss + - postcss - supports-color - typescript - uglify-js - utf-8-validate - webpack-cli - '@docusaurus/plugin-content-docs@3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3)': + '@docusaurus/plugin-content-docs@3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3)': dependencies: - '@docusaurus/core': 3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/logger': 3.10.0 - '@docusaurus/mdx-loader': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/module-type-aliases': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/theme-common': 3.10.0(@docusaurus/plugin-content-docs@3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3))(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/types': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils-common': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils-validation': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/core': 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/logger': 3.10.1 + '@docusaurus/mdx-loader': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/module-type-aliases': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/theme-common': 3.10.1(@docusaurus/plugin-content-docs@3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/types': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils-common': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils-validation': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@types/react-router-config': 5.0.11 combine-promises: 1.2.0 - fs-extra: 11.3.4 + fs-extra: 11.3.5 js-yaml: 4.1.1 lodash: 4.18.1 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) schema-dts: 1.1.5 tslib: 2.8.1 utility-types: 3.11.0 - webpack: 5.106.2 + webpack: 5.107.0(postcss@8.5.15) transitivePeerDependencies: - '@docusaurus/faster' - '@mdx-js/react' + - '@minify-html/node' - '@parcel/css' - '@rspack/core' - '@swc/core' - '@swc/css' + - '@swc/html' - bufferutil + - clean-css + - cssnano - csso - debug - esbuild + - html-minifier-terser - lightningcss + - postcss - supports-color - typescript - uglify-js - utf-8-validate - webpack-cli - '@docusaurus/plugin-content-pages@3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3)': + '@docusaurus/plugin-content-pages@3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3)': dependencies: - '@docusaurus/core': 3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/mdx-loader': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/types': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils-validation': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - fs-extra: 11.3.4 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + '@docusaurus/core': 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/mdx-loader': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/types': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils-validation': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + fs-extra: 11.3.5 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) tslib: 2.8.1 - webpack: 5.106.2 + webpack: 5.107.0(postcss@8.5.15) transitivePeerDependencies: - '@docusaurus/faster' - '@mdx-js/react' + - '@minify-html/node' - '@parcel/css' - '@rspack/core' - '@swc/core' - '@swc/css' + - '@swc/html' - bufferutil + - clean-css + - cssnano - csso - debug - esbuild + - html-minifier-terser - lightningcss + - postcss - supports-color - typescript - uglify-js - utf-8-validate - webpack-cli - '@docusaurus/plugin-css-cascade-layers@3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3)': + '@docusaurus/plugin-css-cascade-layers@3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3)': dependencies: - '@docusaurus/core': 3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/types': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils-validation': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/core': 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/types': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils-validation': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) tslib: 2.8.1 transitivePeerDependencies: - '@docusaurus/faster' - '@mdx-js/react' + - '@minify-html/node' - '@parcel/css' - '@rspack/core' - '@swc/core' - '@swc/css' + - '@swc/html' - bufferutil + - clean-css + - cssnano - csso - debug - esbuild + - html-minifier-terser - lightningcss + - postcss - react - react-dom - supports-color @@ -14433,207 +14635,249 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-debug@3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3)': + '@docusaurus/plugin-debug@3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3)': dependencies: - '@docusaurus/core': 3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/types': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - fs-extra: 11.3.4 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - react-json-view-lite: 2.5.0(react@19.2.5) + '@docusaurus/core': 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/types': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + fs-extra: 11.3.5 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + react-json-view-lite: 2.5.0(react@19.2.6) tslib: 2.8.1 transitivePeerDependencies: - '@docusaurus/faster' - '@mdx-js/react' + - '@minify-html/node' - '@parcel/css' - '@rspack/core' - '@swc/core' - '@swc/css' + - '@swc/html' - bufferutil + - clean-css + - cssnano - csso - debug - esbuild + - html-minifier-terser - lightningcss + - postcss - supports-color - typescript - uglify-js - utf-8-validate - webpack-cli - '@docusaurus/plugin-google-analytics@3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3)': + '@docusaurus/plugin-google-analytics@3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3)': dependencies: - '@docusaurus/core': 3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/types': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils-validation': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + '@docusaurus/core': 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/types': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils-validation': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) tslib: 2.8.1 transitivePeerDependencies: - '@docusaurus/faster' - '@mdx-js/react' + - '@minify-html/node' - '@parcel/css' - '@rspack/core' - '@swc/core' - '@swc/css' + - '@swc/html' - bufferutil + - clean-css + - cssnano - csso - debug - esbuild + - html-minifier-terser - lightningcss + - postcss - supports-color - typescript - uglify-js - utf-8-validate - webpack-cli - '@docusaurus/plugin-google-gtag@3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3)': + '@docusaurus/plugin-google-gtag@3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3)': dependencies: - '@docusaurus/core': 3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/types': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils-validation': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/core': 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/types': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils-validation': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@types/gtag.js': 0.0.20 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) tslib: 2.8.1 transitivePeerDependencies: - '@docusaurus/faster' - '@mdx-js/react' + - '@minify-html/node' - '@parcel/css' - '@rspack/core' - '@swc/core' - '@swc/css' + - '@swc/html' - bufferutil + - clean-css + - cssnano - csso - debug - esbuild + - html-minifier-terser - lightningcss + - postcss - supports-color - typescript - uglify-js - utf-8-validate - webpack-cli - '@docusaurus/plugin-google-tag-manager@3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3)': + '@docusaurus/plugin-google-tag-manager@3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3)': dependencies: - '@docusaurus/core': 3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/types': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils-validation': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + '@docusaurus/core': 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/types': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils-validation': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) tslib: 2.8.1 transitivePeerDependencies: - '@docusaurus/faster' - '@mdx-js/react' + - '@minify-html/node' - '@parcel/css' - '@rspack/core' - '@swc/core' - '@swc/css' + - '@swc/html' - bufferutil + - clean-css + - cssnano - csso - debug - esbuild + - html-minifier-terser - lightningcss + - postcss - supports-color - typescript - uglify-js - utf-8-validate - webpack-cli - '@docusaurus/plugin-sitemap@3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3)': + '@docusaurus/plugin-sitemap@3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3)': dependencies: - '@docusaurus/core': 3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/logger': 3.10.0 - '@docusaurus/types': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils-common': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils-validation': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - fs-extra: 11.3.4 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - sitemap: 7.1.2 + '@docusaurus/core': 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/logger': 3.10.1 + '@docusaurus/types': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils-common': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils-validation': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + fs-extra: 11.3.5 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + sitemap: 7.1.3 tslib: 2.8.1 transitivePeerDependencies: - '@docusaurus/faster' - '@mdx-js/react' + - '@minify-html/node' - '@parcel/css' - '@rspack/core' - '@swc/core' - '@swc/css' + - '@swc/html' - bufferutil + - clean-css + - cssnano - csso - debug - esbuild + - html-minifier-terser - lightningcss + - postcss - supports-color - typescript - uglify-js - utf-8-validate - webpack-cli - '@docusaurus/plugin-svgr@3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3)': + '@docusaurus/plugin-svgr@3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3)': dependencies: - '@docusaurus/core': 3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/types': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils-validation': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/core': 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/types': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils-validation': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@svgr/core': 8.1.0(typescript@6.0.3) '@svgr/webpack': 8.1.0(typescript@6.0.3) - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) tslib: 2.8.1 - webpack: 5.106.2 + webpack: 5.107.0(postcss@8.5.15) transitivePeerDependencies: - '@docusaurus/faster' - '@mdx-js/react' + - '@minify-html/node' - '@parcel/css' - '@rspack/core' - '@swc/core' - '@swc/css' + - '@swc/html' - bufferutil + - clean-css + - cssnano - csso - debug - esbuild + - html-minifier-terser - lightningcss + - postcss - supports-color - typescript - uglify-js - utf-8-validate - webpack-cli - '@docusaurus/preset-classic@3.10.0(@algolia/client-search@5.46.0)(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(search-insights@2.17.3)(typescript@6.0.3)': + '@docusaurus/preset-classic@3.10.1(@algolia/client-search@5.52.1)(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(@types/react@19.2.15)(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(search-insights@2.17.3)(typescript@6.0.3)': dependencies: - '@docusaurus/core': 3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/plugin-content-blog': 3.10.0(@docusaurus/plugin-content-docs@3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3))(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/plugin-content-docs': 3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/plugin-content-pages': 3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/plugin-css-cascade-layers': 3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/plugin-debug': 3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/plugin-google-analytics': 3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/plugin-google-gtag': 3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/plugin-google-tag-manager': 3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/plugin-sitemap': 3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/plugin-svgr': 3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/theme-classic': 3.10.0(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/theme-common': 3.10.0(@docusaurus/plugin-content-docs@3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3))(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/theme-search-algolia': 3.10.0(@algolia/client-search@5.46.0)(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(search-insights@2.17.3)(typescript@6.0.3) - '@docusaurus/types': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + '@docusaurus/core': 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/plugin-content-blog': 3.10.1(@docusaurus/plugin-content-docs@3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3))(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/plugin-content-docs': 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/plugin-content-pages': 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/plugin-css-cascade-layers': 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/plugin-debug': 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/plugin-google-analytics': 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/plugin-google-gtag': 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/plugin-google-tag-manager': 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/plugin-sitemap': 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/plugin-svgr': 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/theme-classic': 3.10.1(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/theme-common': 3.10.1(@docusaurus/plugin-content-docs@3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/theme-search-algolia': 3.10.1(@algolia/client-search@5.52.1)(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(@types/react@19.2.15)(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(search-insights@2.17.3)(typescript@6.0.3) + '@docusaurus/types': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) transitivePeerDependencies: - '@algolia/client-search' - '@docusaurus/faster' - '@mdx-js/react' + - '@minify-html/node' - '@parcel/css' - '@rspack/core' - '@swc/core' - '@swc/css' + - '@swc/html' - '@types/react' - bufferutil + - clean-css + - cssnano - csso - debug - esbuild + - html-minifier-terser - lightningcss + - postcss - search-insights - supports-color - typescript @@ -14641,52 +14885,57 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/react-loadable@6.0.0(react@19.2.5)': + '@docusaurus/react-loadable@6.0.0(react@19.2.6)': dependencies: - '@types/react': 19.2.14 - react: 19.2.5 + '@types/react': 19.2.15 + react: 19.2.6 - '@docusaurus/theme-classic@3.10.0(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3)': + '@docusaurus/theme-classic@3.10.1(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3)': dependencies: - '@docusaurus/core': 3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/logger': 3.10.0 - '@docusaurus/mdx-loader': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/module-type-aliases': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/plugin-content-blog': 3.10.0(@docusaurus/plugin-content-docs@3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3))(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/plugin-content-docs': 3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/plugin-content-pages': 3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/theme-common': 3.10.0(@docusaurus/plugin-content-docs@3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3))(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/theme-translations': 3.10.0 - '@docusaurus/types': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils-common': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils-validation': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@mdx-js/react': 3.1.1(@types/react@19.2.14)(react@19.2.5) + '@docusaurus/core': 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/logger': 3.10.1 + '@docusaurus/mdx-loader': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/module-type-aliases': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/plugin-content-blog': 3.10.1(@docusaurus/plugin-content-docs@3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3))(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/plugin-content-docs': 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/plugin-content-pages': 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/theme-common': 3.10.1(@docusaurus/plugin-content-docs@3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/theme-translations': 3.10.1 + '@docusaurus/types': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils-common': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils-validation': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@mdx-js/react': 3.1.1(@types/react@19.2.15)(react@19.2.6) clsx: 2.1.1 copy-text-to-clipboard: 3.2.2 infima: 0.2.0-alpha.45 lodash: 4.18.1 nprogress: 0.2.0 - postcss: 8.5.12 - prism-react-renderer: 2.4.1(react@19.2.5) + postcss: 8.5.15 + prism-react-renderer: 2.4.1(react@19.2.6) prismjs: 1.30.0 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - react-router-dom: 5.3.4(react@19.2.5) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + react-router-dom: 5.3.4(react@19.2.6) rtlcss: 4.3.0 tslib: 2.8.1 utility-types: 3.11.0 transitivePeerDependencies: - '@docusaurus/faster' + - '@minify-html/node' - '@parcel/css' - '@rspack/core' - '@swc/core' - '@swc/css' + - '@swc/html' - '@types/react' - bufferutil + - clean-css + - cssnano - csso - debug - esbuild + - html-minifier-terser - lightningcss - supports-color - typescript @@ -14694,95 +14943,116 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/theme-common@3.10.0(@docusaurus/plugin-content-docs@3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@docusaurus/theme-common@3.10.1(@docusaurus/plugin-content-docs@3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@docusaurus/mdx-loader': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/module-type-aliases': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/plugin-content-docs': 3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/utils': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils-common': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/mdx-loader': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/module-type-aliases': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/plugin-content-docs': 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/utils': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils-common': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@types/history': 4.7.11 - '@types/react': 19.2.14 + '@types/react': 19.2.15 '@types/react-router-config': 5.0.11 clsx: 2.1.1 parse-numeric-range: 1.3.0 - prism-react-renderer: 2.4.1(react@19.2.5) - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + prism-react-renderer: 2.4.1(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) tslib: 2.8.1 utility-types: 3.11.0 transitivePeerDependencies: + - '@minify-html/node' - '@swc/core' + - '@swc/css' + - '@swc/html' + - clean-css + - cssnano + - csso - esbuild + - html-minifier-terser + - lightningcss + - postcss - supports-color - uglify-js - webpack-cli - '@docusaurus/theme-mermaid@3.10.0(@docusaurus/plugin-content-docs@3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3))(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3)': + '@docusaurus/theme-mermaid@3.10.1(@docusaurus/plugin-content-docs@3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3))(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3)': dependencies: - '@docusaurus/core': 3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/module-type-aliases': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/theme-common': 3.10.0(@docusaurus/plugin-content-docs@3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3))(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/types': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils-validation': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - mermaid: 11.12.2 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + '@docusaurus/core': 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/module-type-aliases': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/theme-common': 3.10.1(@docusaurus/plugin-content-docs@3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/types': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils-validation': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + mermaid: 11.15.0 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) tslib: 2.8.1 transitivePeerDependencies: - '@docusaurus/faster' - '@docusaurus/plugin-content-docs' - '@mdx-js/react' + - '@minify-html/node' - '@parcel/css' - '@rspack/core' - '@swc/core' - '@swc/css' + - '@swc/html' - bufferutil + - clean-css + - cssnano - csso - debug - esbuild + - html-minifier-terser - lightningcss + - postcss - supports-color - typescript - uglify-js - utf-8-validate - webpack-cli - '@docusaurus/theme-search-algolia@3.10.0(@algolia/client-search@5.46.0)(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(search-insights@2.17.3)(typescript@6.0.3)': + '@docusaurus/theme-search-algolia@3.10.1(@algolia/client-search@5.52.1)(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(@types/react@19.2.15)(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(search-insights@2.17.3)(typescript@6.0.3)': dependencies: - '@algolia/autocomplete-core': 1.19.2(@algolia/client-search@5.46.0)(algoliasearch@5.46.0)(search-insights@2.17.3) - '@docsearch/react': 4.3.2(@algolia/client-search@5.46.0)(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(search-insights@2.17.3) - '@docusaurus/core': 3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/logger': 3.10.0 - '@docusaurus/plugin-content-docs': 3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) - '@docusaurus/theme-common': 3.10.0(@docusaurus/plugin-content-docs@3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3))(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/theme-translations': 3.10.0 - '@docusaurus/utils': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils-validation': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - algoliasearch: 5.46.0 - algoliasearch-helper: 3.26.1(algoliasearch@5.46.0) + '@algolia/autocomplete-core': 1.19.8(@algolia/client-search@5.52.1)(algoliasearch@5.52.1)(search-insights@2.17.3) + '@docsearch/react': 4.6.3(@algolia/client-search@5.52.1)(@types/react@19.2.15)(algoliasearch@5.52.1)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(search-insights@2.17.3) + '@docusaurus/core': 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/logger': 3.10.1 + '@docusaurus/plugin-content-docs': 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) + '@docusaurus/theme-common': 3.10.1(@docusaurus/plugin-content-docs@3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/theme-translations': 3.10.1 + '@docusaurus/utils': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils-validation': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + algoliasearch: 5.52.1 + algoliasearch-helper: 3.29.1(algoliasearch@5.52.1) clsx: 2.1.1 eta: 2.2.0 - fs-extra: 11.3.4 + fs-extra: 11.3.5 lodash: 4.18.1 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) tslib: 2.8.1 utility-types: 3.11.0 transitivePeerDependencies: - '@algolia/client-search' - '@docusaurus/faster' - '@mdx-js/react' + - '@minify-html/node' - '@parcel/css' - '@rspack/core' - '@swc/core' - '@swc/css' + - '@swc/html' - '@types/react' - bufferutil + - clean-css + - cssnano - csso - debug - esbuild + - html-minifier-terser - lightningcss + - postcss - search-insights - supports-color - typescript @@ -14790,75 +15060,154 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/theme-translations@3.10.0': + '@docusaurus/theme-translations@3.10.1': dependencies: - fs-extra: 11.3.4 + fs-extra: 11.3.5 tslib: 2.8.1 - '@docusaurus/tsconfig@3.10.0': {} + '@docusaurus/tsconfig@3.10.1': {} - '@docusaurus/types@3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@docusaurus/types@3.10.1(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@mdx-js/mdx': 3.1.1 '@types/history': 4.7.11 '@types/mdast': 4.0.4 - '@types/react': 19.2.14 + '@types/react': 19.2.15 commander: 5.1.0 joi: 17.13.3 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - react-helmet-async: '@slorber/react-helmet-async@1.3.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)' + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + react-helmet-async: '@slorber/react-helmet-async@1.3.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)' utility-types: 3.11.0 - webpack: 5.106.2 + webpack: 5.107.0(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(html-minifier-terser@7.2.0)(postcss@8.5.15) webpack-merge: 5.10.0 transitivePeerDependencies: + - '@minify-html/node' - '@swc/core' + - '@swc/css' + - '@swc/html' + - clean-css + - cssnano + - csso - esbuild + - html-minifier-terser + - lightningcss + - postcss - supports-color - uglify-js - webpack-cli - '@docusaurus/utils-common@3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@docusaurus/types@3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@docusaurus/types': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@mdx-js/mdx': 3.1.1 + '@types/history': 4.7.11 + '@types/mdast': 4.0.4 + '@types/react': 19.2.15 + commander: 5.1.0 + joi: 17.13.3 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + react-helmet-async: '@slorber/react-helmet-async@1.3.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)' + utility-types: 3.11.0 + webpack: 5.107.0(postcss@8.5.15) + webpack-merge: 5.10.0 + transitivePeerDependencies: + - '@minify-html/node' + - '@swc/core' + - '@swc/css' + - '@swc/html' + - clean-css + - cssnano + - csso + - esbuild + - html-minifier-terser + - lightningcss + - postcss + - supports-color + - uglify-js + - webpack-cli + + '@docusaurus/utils-common@3.10.1(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@docusaurus/types': 3.10.1(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) tslib: 2.8.1 transitivePeerDependencies: + - '@minify-html/node' - '@swc/core' + - '@swc/css' + - '@swc/html' + - clean-css + - cssnano + - csso - esbuild + - html-minifier-terser + - lightningcss + - postcss - react - react-dom - supports-color - uglify-js - webpack-cli - '@docusaurus/utils-validation@3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@docusaurus/utils-common@3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@docusaurus/logger': 3.10.0 - '@docusaurus/utils': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils-common': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - fs-extra: 11.3.4 + '@docusaurus/types': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + tslib: 2.8.1 + transitivePeerDependencies: + - '@minify-html/node' + - '@swc/core' + - '@swc/css' + - '@swc/html' + - clean-css + - cssnano + - csso + - esbuild + - html-minifier-terser + - lightningcss + - postcss + - react + - react-dom + - supports-color + - uglify-js + - webpack-cli + + '@docusaurus/utils-validation@3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@docusaurus/logger': 3.10.1 + '@docusaurus/utils': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils-common': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + fs-extra: 11.3.5 joi: 17.13.3 js-yaml: 4.1.1 lodash: 4.18.1 tslib: 2.8.1 transitivePeerDependencies: + - '@minify-html/node' - '@swc/core' + - '@swc/css' + - '@swc/html' + - clean-css + - cssnano + - csso - esbuild + - html-minifier-terser + - lightningcss + - postcss - react - react-dom - supports-color - uglify-js - webpack-cli - '@docusaurus/utils@3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@docusaurus/utils@3.10.1(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@docusaurus/logger': 3.10.0 - '@docusaurus/types': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@docusaurus/utils-common': 3.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/logger': 3.10.1 + '@docusaurus/types': 3.10.1(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils-common': 3.10.1(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) escape-string-regexp: 4.0.0 execa: 5.1.1 - file-loader: 6.2.0(webpack@5.106.2) - fs-extra: 11.3.4 + file-loader: 6.2.0(webpack@5.107.0(postcss@8.5.15)) + fs-extra: 11.3.5 github-slugger: 1.5.0 globby: 11.1.0 gray-matter: 4.0.3 @@ -14870,12 +15219,62 @@ snapshots: prompts: 2.4.2 resolve-pathname: 3.0.0 tslib: 2.8.1 - url-loader: 4.1.1(file-loader@6.2.0(webpack@5.106.2))(webpack@5.106.2) + url-loader: 4.1.1(file-loader@6.2.0(webpack@5.107.0(postcss@8.5.15)))(webpack@5.107.0(postcss@8.5.15)) utility-types: 3.11.0 - webpack: 5.106.2 + webpack: 5.107.0(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(html-minifier-terser@7.2.0)(postcss@8.5.15) transitivePeerDependencies: + - '@minify-html/node' - '@swc/core' + - '@swc/css' + - '@swc/html' + - clean-css + - cssnano + - csso - esbuild + - html-minifier-terser + - lightningcss + - postcss + - react + - react-dom + - supports-color + - uglify-js + - webpack-cli + + '@docusaurus/utils@3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@docusaurus/logger': 3.10.1 + '@docusaurus/types': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@docusaurus/utils-common': 3.10.1(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + escape-string-regexp: 4.0.0 + execa: 5.1.1 + file-loader: 6.2.0(webpack@5.107.0(postcss@8.5.15)) + fs-extra: 11.3.5 + github-slugger: 1.5.0 + globby: 11.1.0 + gray-matter: 4.0.3 + jiti: 1.21.7 + js-yaml: 4.1.1 + lodash: 4.18.1 + micromatch: 4.0.8 + p-queue: 6.6.2 + prompts: 2.4.2 + resolve-pathname: 3.0.0 + tslib: 2.8.1 + url-loader: 4.1.1(file-loader@6.2.0(webpack@5.107.0(postcss@8.5.15)))(webpack@5.107.0(postcss@8.5.15)) + utility-types: 3.11.0 + webpack: 5.107.0(postcss@8.5.15) + transitivePeerDependencies: + - '@minify-html/node' + - '@swc/core' + - '@swc/css' + - '@swc/html' + - clean-css + - cssnano + - csso + - esbuild + - html-minifier-terser + - lightningcss + - postcss - react - react-dom - supports-color @@ -14904,6 +15303,9 @@ snapshots: '@esbuild/aix-ppc64@0.27.4': optional: true + '@esbuild/aix-ppc64@0.27.7': + optional: true + '@esbuild/aix-ppc64@0.28.0': optional: true @@ -14913,6 +15315,9 @@ snapshots: '@esbuild/android-arm64@0.27.4': optional: true + '@esbuild/android-arm64@0.27.7': + optional: true + '@esbuild/android-arm64@0.28.0': optional: true @@ -14922,6 +15327,9 @@ snapshots: '@esbuild/android-arm@0.27.4': optional: true + '@esbuild/android-arm@0.27.7': + optional: true + '@esbuild/android-arm@0.28.0': optional: true @@ -14931,6 +15339,9 @@ snapshots: '@esbuild/android-x64@0.27.4': optional: true + '@esbuild/android-x64@0.27.7': + optional: true + '@esbuild/android-x64@0.28.0': optional: true @@ -14940,6 +15351,9 @@ snapshots: '@esbuild/darwin-arm64@0.27.4': optional: true + '@esbuild/darwin-arm64@0.27.7': + optional: true + '@esbuild/darwin-arm64@0.28.0': optional: true @@ -14949,6 +15363,9 @@ snapshots: '@esbuild/darwin-x64@0.27.4': optional: true + '@esbuild/darwin-x64@0.27.7': + optional: true + '@esbuild/darwin-x64@0.28.0': optional: true @@ -14958,6 +15375,9 @@ snapshots: '@esbuild/freebsd-arm64@0.27.4': optional: true + '@esbuild/freebsd-arm64@0.27.7': + optional: true + '@esbuild/freebsd-arm64@0.28.0': optional: true @@ -14967,6 +15387,9 @@ snapshots: '@esbuild/freebsd-x64@0.27.4': optional: true + '@esbuild/freebsd-x64@0.27.7': + optional: true + '@esbuild/freebsd-x64@0.28.0': optional: true @@ -14976,6 +15399,9 @@ snapshots: '@esbuild/linux-arm64@0.27.4': optional: true + '@esbuild/linux-arm64@0.27.7': + optional: true + '@esbuild/linux-arm64@0.28.0': optional: true @@ -14985,6 +15411,9 @@ snapshots: '@esbuild/linux-arm@0.27.4': optional: true + '@esbuild/linux-arm@0.27.7': + optional: true + '@esbuild/linux-arm@0.28.0': optional: true @@ -14994,6 +15423,9 @@ snapshots: '@esbuild/linux-ia32@0.27.4': optional: true + '@esbuild/linux-ia32@0.27.7': + optional: true + '@esbuild/linux-ia32@0.28.0': optional: true @@ -15003,6 +15435,9 @@ snapshots: '@esbuild/linux-loong64@0.27.4': optional: true + '@esbuild/linux-loong64@0.27.7': + optional: true + '@esbuild/linux-loong64@0.28.0': optional: true @@ -15012,6 +15447,9 @@ snapshots: '@esbuild/linux-mips64el@0.27.4': optional: true + '@esbuild/linux-mips64el@0.27.7': + optional: true + '@esbuild/linux-mips64el@0.28.0': optional: true @@ -15021,6 +15459,9 @@ snapshots: '@esbuild/linux-ppc64@0.27.4': optional: true + '@esbuild/linux-ppc64@0.27.7': + optional: true + '@esbuild/linux-ppc64@0.28.0': optional: true @@ -15030,6 +15471,9 @@ snapshots: '@esbuild/linux-riscv64@0.27.4': optional: true + '@esbuild/linux-riscv64@0.27.7': + optional: true + '@esbuild/linux-riscv64@0.28.0': optional: true @@ -15039,6 +15483,9 @@ snapshots: '@esbuild/linux-s390x@0.27.4': optional: true + '@esbuild/linux-s390x@0.27.7': + optional: true + '@esbuild/linux-s390x@0.28.0': optional: true @@ -15048,12 +15495,18 @@ snapshots: '@esbuild/linux-x64@0.27.4': optional: true + '@esbuild/linux-x64@0.27.7': + optional: true + '@esbuild/linux-x64@0.28.0': optional: true '@esbuild/netbsd-arm64@0.27.4': optional: true + '@esbuild/netbsd-arm64@0.27.7': + optional: true + '@esbuild/netbsd-arm64@0.28.0': optional: true @@ -15063,12 +15516,18 @@ snapshots: '@esbuild/netbsd-x64@0.27.4': optional: true + '@esbuild/netbsd-x64@0.27.7': + optional: true + '@esbuild/netbsd-x64@0.28.0': optional: true '@esbuild/openbsd-arm64@0.27.4': optional: true + '@esbuild/openbsd-arm64@0.27.7': + optional: true + '@esbuild/openbsd-arm64@0.28.0': optional: true @@ -15078,12 +15537,18 @@ snapshots: '@esbuild/openbsd-x64@0.27.4': optional: true + '@esbuild/openbsd-x64@0.27.7': + optional: true + '@esbuild/openbsd-x64@0.28.0': optional: true '@esbuild/openharmony-arm64@0.27.4': optional: true + '@esbuild/openharmony-arm64@0.27.7': + optional: true + '@esbuild/openharmony-arm64@0.28.0': optional: true @@ -15093,6 +15558,9 @@ snapshots: '@esbuild/sunos-x64@0.27.4': optional: true + '@esbuild/sunos-x64@0.27.7': + optional: true + '@esbuild/sunos-x64@0.28.0': optional: true @@ -15102,6 +15570,9 @@ snapshots: '@esbuild/win32-arm64@0.27.4': optional: true + '@esbuild/win32-arm64@0.27.7': + optional: true + '@esbuild/win32-arm64@0.28.0': optional: true @@ -15111,6 +15582,9 @@ snapshots: '@esbuild/win32-ia32@0.27.4': optional: true + '@esbuild/win32-ia32@0.27.7': + optional: true + '@esbuild/win32-ia32@0.28.0': optional: true @@ -15120,12 +15594,15 @@ snapshots: '@esbuild/win32-x64@0.27.4': optional: true + '@esbuild/win32-x64@0.27.7': + optional: true + '@esbuild/win32-x64@0.28.0': optional: true - '@eslint-community/eslint-utils@4.9.1(eslint@10.2.1(jiti@2.6.1))': + '@eslint-community/eslint-utils@4.9.1(eslint@10.4.0(jiti@2.7.0))': dependencies: - eslint: 10.2.1(jiti@2.6.1) + eslint: 10.4.0(jiti@2.7.0) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} @@ -15138,7 +15615,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/config-helpers@0.5.5': + '@eslint/config-helpers@0.6.0': dependencies: '@eslint/core': 1.2.1 @@ -15146,14 +15623,14 @@ snapshots: dependencies: '@types/json-schema': 7.0.15 - '@eslint/css-tree@4.0.2': + '@eslint/css-tree@4.0.3': dependencies: - mdn-data: 2.27.1 + mdn-data: 2.28.0 source-map-js: 1.2.1 - '@eslint/js@10.0.1(eslint@10.2.1(jiti@2.6.1))': + '@eslint/js@10.0.1(eslint@10.4.0(jiti@2.7.0))': optionalDependencies: - eslint: 10.2.1(jiti@2.6.1) + eslint: 10.4.0(jiti@2.7.0) '@eslint/object-schema@3.0.5': {} @@ -15197,7 +15674,7 @@ snapshots: dependencies: tslib: 2.8.1 - '@formatjs/fast-memoize@3.1.2': {} + '@formatjs/fast-memoize@3.1.5': {} '@formatjs/icu-messageformat-parser@2.11.4': dependencies: @@ -15205,16 +15682,16 @@ snapshots: '@formatjs/icu-skeleton-parser': 1.8.16 tslib: 2.8.1 - '@formatjs/icu-messageformat-parser@3.5.4': + '@formatjs/icu-messageformat-parser@3.5.9': dependencies: - '@formatjs/icu-skeleton-parser': 2.1.4 + '@formatjs/icu-skeleton-parser': 2.1.9 '@formatjs/icu-skeleton-parser@1.8.16': dependencies: '@formatjs/ecma402-abstract': 2.3.6 tslib: 2.8.1 - '@formatjs/icu-skeleton-parser@2.1.4': {} + '@formatjs/icu-skeleton-parser@2.1.9': {} '@formatjs/intl-localematcher@0.6.2': dependencies: @@ -15230,29 +15707,29 @@ snapshots: dependencies: '@fortawesome/fontawesome-common-types': 7.2.0 - '@golevelup/nestjs-discovery@5.0.0(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)': + '@golevelup/nestjs-discovery@5.0.0(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)': dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.21(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.21)(@nestjs/websockets@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2) lodash: 4.18.1 '@grpc/grpc-js@1.14.3': dependencies: - '@grpc/proto-loader': 0.8.0 + '@grpc/proto-loader': 0.8.1 '@js-sdsl/ordered-map': 4.4.2 '@grpc/proto-loader@0.7.15': dependencies: lodash.camelcase: 4.3.0 long: 5.3.2 - protobufjs: 7.5.5 + protobufjs: 7.6.0 yargs: 17.7.2 - '@grpc/proto-loader@0.8.0': + '@grpc/proto-loader@0.8.1': dependencies: lodash.camelcase: 4.3.0 long: 5.3.2 - protobufjs: 7.5.5 + protobufjs: 7.6.0 yargs: 17.7.2 '@hapi/hoek@9.3.0': {} @@ -15279,13 +15756,13 @@ snapshots: '@iconify/types@2.0.0': {} - '@iconify/utils@3.1.0': + '@iconify/utils@3.1.3': dependencies: '@antfu/install-pkg': 1.1.0 '@iconify/types': 2.0.0 - mlly: 1.8.0 + import-meta-resolve: 4.2.0 - '@img/colour@1.0.0': {} + '@img/colour@1.1.0': {} '@img/sharp-darwin-arm64@0.34.5': optionalDependencies: @@ -15387,177 +15864,168 @@ snapshots: dependencies: commander: 14.0.3 graph-data-structure: 4.5.0 - kysely: 0.28.16 - kysely-postgres-js: 3.0.0(kysely@0.28.16)(postgres@3.4.9) - pg-connection-string: 2.12.0 + kysely: 0.28.17 + kysely-postgres-js: 3.0.0(kysely@0.28.17)(postgres@3.4.9) + pg-connection-string: 2.13.0 postgres: 3.4.9 - '@immich/svelte-markdown-preprocess@0.4.1(svelte@5.55.2)': + '@immich/ui@0.79.3(@sveltejs/kit@2.60.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.55.8(@typescript-eslint/types@8.59.4))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4))(typescript@6.0.3)(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4))': dependencies: - front-matter: 4.0.2 - marked: 17.0.6 - node-emoji: 2.2.0 - svelte: 5.55.2 - - '@immich/ui@0.76.2(@sveltejs/kit@2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)': - dependencies: - '@immich/svelte-markdown-preprocess': 0.4.1(svelte@5.55.2) - '@internationalized/date': 3.12.1 + '@internationalized/date': 3.12.2 '@mdi/js': 7.4.47 - bits-ui: 2.18.0(@internationalized/date@3.12.1)(@sveltejs/kit@2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2) + '@sveltejs/kit': 2.60.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.55.8(@typescript-eslint/types@8.59.4))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4))(typescript@6.0.3)(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)) + bits-ui: 2.18.1(@internationalized/date@3.12.2)(@sveltejs/kit@2.60.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.55.8(@typescript-eslint/types@8.59.4))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4))(typescript@6.0.3)(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4)) luxon: 3.7.2 - simple-icons: 16.17.0 - svelte: 5.55.2 + simple-icons: 16.20.0 + svelte: 5.55.8(@typescript-eslint/types@8.59.4) svelte-highlight: 7.9.0 - tailwind-merge: 3.5.0 - tailwind-variants: 3.2.2(tailwind-merge@3.5.0)(tailwindcss@4.2.4) - tailwindcss: 4.2.4 - transitivePeerDependencies: - - '@sveltejs/kit' + tailwind-merge: 3.6.0 + tailwind-variants: 3.2.2(tailwind-merge@3.6.0)(tailwindcss@4.3.0) + tailwindcss: 4.3.0 '@inquirer/ansi@1.0.2': {} - '@inquirer/checkbox@4.3.2(@types/node@24.12.2)': + '@inquirer/checkbox@4.3.2(@types/node@24.12.4)': dependencies: '@inquirer/ansi': 1.0.2 - '@inquirer/core': 10.3.2(@types/node@24.12.2) + '@inquirer/core': 10.3.2(@types/node@24.12.4) '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@24.12.2) + '@inquirer/type': 3.0.10(@types/node@24.12.4) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 - '@inquirer/confirm@5.1.21(@types/node@24.12.2)': + '@inquirer/confirm@5.1.21(@types/node@24.12.4)': dependencies: - '@inquirer/core': 10.3.2(@types/node@24.12.2) - '@inquirer/type': 3.0.10(@types/node@24.12.2) + '@inquirer/core': 10.3.2(@types/node@24.12.4) + '@inquirer/type': 3.0.10(@types/node@24.12.4) optionalDependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 - '@inquirer/core@10.3.2(@types/node@24.12.2)': + '@inquirer/core@10.3.2(@types/node@24.12.4)': dependencies: '@inquirer/ansi': 1.0.2 '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@24.12.2) + '@inquirer/type': 3.0.10(@types/node@24.12.4) cli-width: 4.1.0 mute-stream: 2.0.0 signal-exit: 4.1.0 wrap-ansi: 6.2.0 yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 - '@inquirer/editor@4.2.23(@types/node@24.12.2)': + '@inquirer/editor@4.2.23(@types/node@24.12.4)': dependencies: - '@inquirer/core': 10.3.2(@types/node@24.12.2) - '@inquirer/external-editor': 1.0.3(@types/node@24.12.2) - '@inquirer/type': 3.0.10(@types/node@24.12.2) + '@inquirer/core': 10.3.2(@types/node@24.12.4) + '@inquirer/external-editor': 1.0.3(@types/node@24.12.4) + '@inquirer/type': 3.0.10(@types/node@24.12.4) optionalDependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 - '@inquirer/expand@4.0.23(@types/node@24.12.2)': + '@inquirer/expand@4.0.23(@types/node@24.12.4)': dependencies: - '@inquirer/core': 10.3.2(@types/node@24.12.2) - '@inquirer/type': 3.0.10(@types/node@24.12.2) + '@inquirer/core': 10.3.2(@types/node@24.12.4) + '@inquirer/type': 3.0.10(@types/node@24.12.4) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 - '@inquirer/external-editor@1.0.3(@types/node@24.12.2)': + '@inquirer/external-editor@1.0.3(@types/node@24.12.4)': dependencies: chardet: 2.1.1 iconv-lite: 0.7.2 optionalDependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@inquirer/figures@1.0.15': {} - '@inquirer/input@4.3.1(@types/node@24.12.2)': + '@inquirer/input@4.3.1(@types/node@24.12.4)': dependencies: - '@inquirer/core': 10.3.2(@types/node@24.12.2) - '@inquirer/type': 3.0.10(@types/node@24.12.2) + '@inquirer/core': 10.3.2(@types/node@24.12.4) + '@inquirer/type': 3.0.10(@types/node@24.12.4) optionalDependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 - '@inquirer/number@3.0.23(@types/node@24.12.2)': + '@inquirer/number@3.0.23(@types/node@24.12.4)': dependencies: - '@inquirer/core': 10.3.2(@types/node@24.12.2) - '@inquirer/type': 3.0.10(@types/node@24.12.2) + '@inquirer/core': 10.3.2(@types/node@24.12.4) + '@inquirer/type': 3.0.10(@types/node@24.12.4) optionalDependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 - '@inquirer/password@4.0.23(@types/node@24.12.2)': + '@inquirer/password@4.0.23(@types/node@24.12.4)': dependencies: '@inquirer/ansi': 1.0.2 - '@inquirer/core': 10.3.2(@types/node@24.12.2) - '@inquirer/type': 3.0.10(@types/node@24.12.2) + '@inquirer/core': 10.3.2(@types/node@24.12.4) + '@inquirer/type': 3.0.10(@types/node@24.12.4) optionalDependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 - '@inquirer/prompts@7.10.1(@types/node@24.12.2)': + '@inquirer/prompts@7.10.1(@types/node@24.12.4)': dependencies: - '@inquirer/checkbox': 4.3.2(@types/node@24.12.2) - '@inquirer/confirm': 5.1.21(@types/node@24.12.2) - '@inquirer/editor': 4.2.23(@types/node@24.12.2) - '@inquirer/expand': 4.0.23(@types/node@24.12.2) - '@inquirer/input': 4.3.1(@types/node@24.12.2) - '@inquirer/number': 3.0.23(@types/node@24.12.2) - '@inquirer/password': 4.0.23(@types/node@24.12.2) - '@inquirer/rawlist': 4.1.11(@types/node@24.12.2) - '@inquirer/search': 3.2.2(@types/node@24.12.2) - '@inquirer/select': 4.4.2(@types/node@24.12.2) + '@inquirer/checkbox': 4.3.2(@types/node@24.12.4) + '@inquirer/confirm': 5.1.21(@types/node@24.12.4) + '@inquirer/editor': 4.2.23(@types/node@24.12.4) + '@inquirer/expand': 4.0.23(@types/node@24.12.4) + '@inquirer/input': 4.3.1(@types/node@24.12.4) + '@inquirer/number': 3.0.23(@types/node@24.12.4) + '@inquirer/password': 4.0.23(@types/node@24.12.4) + '@inquirer/rawlist': 4.1.11(@types/node@24.12.4) + '@inquirer/search': 3.2.2(@types/node@24.12.4) + '@inquirer/select': 4.4.2(@types/node@24.12.4) optionalDependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 - '@inquirer/prompts@7.3.2(@types/node@24.12.2)': + '@inquirer/prompts@7.3.2(@types/node@24.12.4)': dependencies: - '@inquirer/checkbox': 4.3.2(@types/node@24.12.2) - '@inquirer/confirm': 5.1.21(@types/node@24.12.2) - '@inquirer/editor': 4.2.23(@types/node@24.12.2) - '@inquirer/expand': 4.0.23(@types/node@24.12.2) - '@inquirer/input': 4.3.1(@types/node@24.12.2) - '@inquirer/number': 3.0.23(@types/node@24.12.2) - '@inquirer/password': 4.0.23(@types/node@24.12.2) - '@inquirer/rawlist': 4.1.11(@types/node@24.12.2) - '@inquirer/search': 3.2.2(@types/node@24.12.2) - '@inquirer/select': 4.4.2(@types/node@24.12.2) + '@inquirer/checkbox': 4.3.2(@types/node@24.12.4) + '@inquirer/confirm': 5.1.21(@types/node@24.12.4) + '@inquirer/editor': 4.2.23(@types/node@24.12.4) + '@inquirer/expand': 4.0.23(@types/node@24.12.4) + '@inquirer/input': 4.3.1(@types/node@24.12.4) + '@inquirer/number': 3.0.23(@types/node@24.12.4) + '@inquirer/password': 4.0.23(@types/node@24.12.4) + '@inquirer/rawlist': 4.1.11(@types/node@24.12.4) + '@inquirer/search': 3.2.2(@types/node@24.12.4) + '@inquirer/select': 4.4.2(@types/node@24.12.4) optionalDependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 - '@inquirer/rawlist@4.1.11(@types/node@24.12.2)': + '@inquirer/rawlist@4.1.11(@types/node@24.12.4)': dependencies: - '@inquirer/core': 10.3.2(@types/node@24.12.2) - '@inquirer/type': 3.0.10(@types/node@24.12.2) + '@inquirer/core': 10.3.2(@types/node@24.12.4) + '@inquirer/type': 3.0.10(@types/node@24.12.4) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 - '@inquirer/search@3.2.2(@types/node@24.12.2)': + '@inquirer/search@3.2.2(@types/node@24.12.4)': dependencies: - '@inquirer/core': 10.3.2(@types/node@24.12.2) + '@inquirer/core': 10.3.2(@types/node@24.12.4) '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@24.12.2) + '@inquirer/type': 3.0.10(@types/node@24.12.4) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 - '@inquirer/select@4.4.2(@types/node@24.12.2)': + '@inquirer/select@4.4.2(@types/node@24.12.4)': dependencies: '@inquirer/ansi': 1.0.2 - '@inquirer/core': 10.3.2(@types/node@24.12.2) + '@inquirer/core': 10.3.2(@types/node@24.12.4) '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@24.12.2) + '@inquirer/type': 3.0.10(@types/node@24.12.4) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 - '@inquirer/type@3.0.10(@types/node@24.12.2)': + '@inquirer/type@3.0.10(@types/node@24.12.4)': optionalDependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 - '@internationalized/date@3.12.1': + '@internationalized/date@3.12.2': dependencies: - '@swc/helpers': 0.5.21 + '@swc/helpers': 0.5.23 '@ioredis/commands@1.5.1': {} @@ -15574,18 +16042,16 @@ snapshots: dependencies: minipass: 7.1.3 - '@istanbuljs/schema@0.1.6': {} - '@jest/schemas@29.6.3': dependencies: - '@sinclair/typebox': 0.27.8 + '@sinclair/typebox': 0.27.10 '@jest/types@29.6.3': dependencies: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@types/yargs': 17.0.35 chalk: 4.1.2 @@ -15651,7 +16117,7 @@ snapshots: dependencies: '@jsonjoy.com/fs-node-builtins': 4.57.2(tslib@2.8.1) '@jsonjoy.com/fs-node-utils': 4.57.2(tslib@2.8.1) - thingies: 2.5.0(tslib@2.8.1) + thingies: 2.6.0(tslib@2.8.1) tslib: 2.8.1 '@jsonjoy.com/fs-fsa@4.57.2(tslib@2.8.1)': @@ -15659,7 +16125,7 @@ snapshots: '@jsonjoy.com/fs-core': 4.57.2(tslib@2.8.1) '@jsonjoy.com/fs-node-builtins': 4.57.2(tslib@2.8.1) '@jsonjoy.com/fs-node-utils': 4.57.2(tslib@2.8.1) - thingies: 2.5.0(tslib@2.8.1) + thingies: 2.6.0(tslib@2.8.1) tslib: 2.8.1 '@jsonjoy.com/fs-node-builtins@4.57.2(tslib@2.8.1)': @@ -15686,7 +16152,7 @@ snapshots: '@jsonjoy.com/fs-print': 4.57.2(tslib@2.8.1) '@jsonjoy.com/fs-snapshot': 4.57.2(tslib@2.8.1) glob-to-regex.js: 1.2.0(tslib@2.8.1) - thingies: 2.5.0(tslib@2.8.1) + thingies: 2.6.0(tslib@2.8.1) tslib: 2.8.1 '@jsonjoy.com/fs-print@4.57.2(tslib@2.8.1)': @@ -15711,7 +16177,7 @@ snapshots: '@jsonjoy.com/json-pointer': 1.0.2(tslib@2.8.1) '@jsonjoy.com/util': 1.9.0(tslib@2.8.1) hyperdyperid: 1.2.0 - thingies: 2.5.0(tslib@2.8.1) + thingies: 2.6.0(tslib@2.8.1) tree-dump: 1.1.0(tslib@2.8.1) tslib: 2.8.1 @@ -15723,7 +16189,7 @@ snapshots: '@jsonjoy.com/json-pointer': 17.67.0(tslib@2.8.1) '@jsonjoy.com/util': 17.67.0(tslib@2.8.1) hyperdyperid: 1.2.0 - thingies: 2.5.0(tslib@2.8.1) + thingies: 2.6.0(tslib@2.8.1) tree-dump: 1.1.0(tslib@2.8.1) tslib: 2.8.1 @@ -15756,7 +16222,7 @@ snapshots: dependencies: vary: 1.1.2 - '@koa/router@15.4.0(koa@3.2.0)': + '@koa/router@15.5.0(koa@3.2.0)': dependencies: debug: 4.4.3 http-errors: 2.0.1 @@ -15766,11 +16232,11 @@ snapshots: transitivePeerDependencies: - supports-color - '@koddsson/eslint-plugin-tscompat@0.2.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3)': + '@koddsson/eslint-plugin-tscompat@0.2.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: '@mdn/browser-compat-data': 6.1.5 - '@typescript-eslint/type-utils': 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) - '@typescript-eslint/utils': 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + '@typescript-eslint/type-utils': 8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/utils': 8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) browserslist: 4.28.2 transitivePeerDependencies: - eslint @@ -15816,23 +16282,7 @@ snapshots: nopt: 5.0.0 npmlog: 5.0.1 rimraf: 3.0.2 - semver: 7.7.4 - tar: 6.2.1 - transitivePeerDependencies: - - encoding - - supports-color - optional: true - - '@mapbox/node-pre-gyp@1.0.11(encoding@0.1.13)': - dependencies: - detect-libc: 2.1.2 - https-proxy-agent: 5.0.1 - make-dir: 3.1.0 - node-fetch: 2.7.0(encoding@0.1.13) - nopt: 5.0.0 - npmlog: 5.0.1 - rimraf: 3.0.2 - semver: 7.7.4 + semver: 7.8.1 tar: 6.2.1 transitivePeerDependencies: - encoding @@ -15840,7 +16290,7 @@ snapshots: '@mapbox/point-geometry@1.1.0': {} - '@mapbox/tiny-sdf@2.1.0': {} + '@mapbox/tiny-sdf@2.2.0': {} '@mapbox/unitbezier@0.0.1': {} @@ -15856,16 +16306,15 @@ snapshots: '@maplibre/geojson-vt@6.1.0': dependencies: - kdbush: 4.0.2 + kdbush: 4.1.0 - '@maplibre/maplibre-gl-style-spec@24.8.1': + '@maplibre/maplibre-gl-style-spec@24.8.5': dependencies: '@mapbox/jsonlint-lines-primitives': 2.0.2 '@mapbox/unitbezier': 0.0.1 json-stringify-pretty-compact: 4.0.0 minimist: 1.2.8 quickselect: 3.0.0 - rw: 1.3.3 tinyqueue: 3.0.0 '@maplibre/mlt@1.1.9': @@ -15896,7 +16345,7 @@ snapshots: '@mdx-js/mdx@3.1.1': dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 '@types/estree-jsx': 1.0.5 '@types/hast': 3.0.4 '@types/mdx': 2.0.13 @@ -15919,20 +16368,20 @@ snapshots: unified: 11.0.5 unist-util-position-from-estree: 2.0.0 unist-util-stringify-position: 4.0.0 - unist-util-visit: 5.0.0 + unist-util-visit: 5.1.0 vfile: 6.0.3 transitivePeerDependencies: - supports-color - '@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5)': + '@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6)': dependencies: '@types/mdx': 2.0.13 - '@types/react': 19.2.14 - react: 19.2.5 + '@types/react': 19.2.15 + react: 19.2.6 - '@mermaid-js/parser@0.6.3': + '@mermaid-js/parser@1.1.1': dependencies: - langium: 3.3.1 + '@chevrotain/types': 11.1.2 '@microsoft/tsdoc@0.16.0': {} @@ -15960,53 +16409,62 @@ snapshots: dependencies: '@emnapi/core': 1.10.0 '@emnapi/runtime': 1.10.0 - '@tybys/wasm-util': 0.10.1 + '@tybys/wasm-util': 0.10.2 optional: true - '@nestjs/bull-shared@11.0.4(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)': + '@nestjs/bull-shared@11.0.4(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)': dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.21(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.21)(@nestjs/websockets@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2) tslib: 2.8.1 - '@nestjs/bullmq@11.0.4(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(bullmq@5.76.1)': + '@nestjs/bullmq@11.0.4(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(bullmq@5.76.10)': dependencies: - '@nestjs/bull-shared': 11.0.4(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19) - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) - bullmq: 5.76.1 + '@nestjs/bull-shared': 11.0.4(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21) + '@nestjs/common': 11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.21(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.21)(@nestjs/websockets@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2) + bullmq: 5.76.10 tslib: 2.8.1 - '@nestjs/cli@11.0.21(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@24.12.2)(esbuild@0.28.0)(prettier@3.8.3)': + '@nestjs/cli@11.0.21(@swc/core@1.15.33(@swc/helpers@0.5.23))(@types/node@24.12.4)(esbuild@0.28.0)(lightningcss@1.32.0)(prettier@3.8.3)': dependencies: '@angular-devkit/core': 19.2.24(chokidar@4.0.3) '@angular-devkit/schematics': 19.2.24(chokidar@4.0.3) - '@angular-devkit/schematics-cli': 19.2.24(@types/node@24.12.2)(chokidar@4.0.3) - '@inquirer/prompts': 7.10.1(@types/node@24.12.2) + '@angular-devkit/schematics-cli': 19.2.24(@types/node@24.12.4)(chokidar@4.0.3) + '@inquirer/prompts': 7.10.1(@types/node@24.12.4) '@nestjs/schematics': 11.1.0(chokidar@4.0.3)(prettier@3.8.3)(typescript@5.9.3) ansis: 4.2.0 chokidar: 4.0.3 cli-table3: 0.6.5 commander: 4.1.1 - fork-ts-checker-webpack-plugin: 9.1.0(typescript@5.9.3)(webpack@5.106.0(@swc/core@1.15.30(@swc/helpers@0.5.21))(esbuild@0.28.0)) + fork-ts-checker-webpack-plugin: 9.1.0(typescript@5.9.3)(webpack@5.106.0(@swc/core@1.15.33(@swc/helpers@0.5.23))(esbuild@0.28.0)(lightningcss@1.32.0)) glob: 13.0.6 node-emoji: 1.11.0 ora: 5.4.1 tsconfig-paths: 4.2.0 tsconfig-paths-webpack-plugin: 4.2.0 typescript: 5.9.3 - webpack: 5.106.0(@swc/core@1.15.30(@swc/helpers@0.5.21))(esbuild@0.28.0) + webpack: 5.106.0(@swc/core@1.15.33(@swc/helpers@0.5.23))(esbuild@0.28.0)(lightningcss@1.32.0) webpack-node-externals: 3.0.0 optionalDependencies: - '@swc/core': 1.15.30(@swc/helpers@0.5.21) + '@swc/core': 1.15.33(@swc/helpers@0.5.23) transitivePeerDependencies: + - '@minify-html/node' + - '@swc/css' + - '@swc/html' - '@types/node' + - clean-css + - cssnano + - csso - esbuild + - html-minifier-terser + - lightningcss + - postcss - prettier - uglify-js - webpack-cli - '@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)': + '@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2)': dependencies: file-type: 21.3.4 iterare: 1.2.1 @@ -16015,14 +16473,12 @@ snapshots: rxjs: 7.8.2 tslib: 2.8.1 uid: 2.0.2 - optionalDependencies: - class-transformer: 0.5.1 transitivePeerDependencies: - supports-color - '@nestjs/core@11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2)': + '@nestjs/core@11.1.21(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.21)(@nestjs/websockets@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2)': dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nuxt/opencollective': 0.4.1 fast-safe-stringify: 2.1.1 iterare: 1.2.1 @@ -16032,20 +16488,18 @@ snapshots: tslib: 2.8.1 uid: 2.0.2 optionalDependencies: - '@nestjs/platform-express': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19) - '@nestjs/websockets': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/platform-socket.io@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/platform-express': 11.1.21(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21) + '@nestjs/websockets': 11.1.21(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(@nestjs/platform-socket.io@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/mapped-types@2.1.1(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(class-transformer@0.5.1)(reflect-metadata@0.2.2)': + '@nestjs/mapped-types@2.1.1(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)': dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2) reflect-metadata: 0.2.2 - optionalDependencies: - class-transformer: 0.5.1 - '@nestjs/platform-express@11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)': + '@nestjs/platform-express@11.1.21(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)': dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.21(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.21)(@nestjs/websockets@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2) cors: 2.8.6 express: 5.2.1 multer: 2.1.1 @@ -16054,10 +16508,10 @@ snapshots: transitivePeerDependencies: - supports-color - '@nestjs/platform-socket.io@11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.19)(rxjs@7.8.2)': + '@nestjs/platform-socket.io@11.1.21(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.21)(rxjs@7.8.2)': dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/websockets': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/platform-socket.io@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/websockets': 11.1.21(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(@nestjs/platform-socket.io@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2) rxjs: 7.8.2 socket.io: 4.8.3 tslib: 2.8.1 @@ -16066,10 +16520,10 @@ snapshots: - supports-color - utf-8-validate - '@nestjs/schedule@6.1.3(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)': + '@nestjs/schedule@6.1.3(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)': dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.21(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.21)(@nestjs/websockets@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2) cron: 4.4.0 '@nestjs/schematics@11.1.0(chokidar@4.0.3)(prettier@3.8.3)(typescript@5.9.3)': @@ -16098,39 +16552,40 @@ snapshots: transitivePeerDependencies: - chokidar - '@nestjs/swagger@11.4.2(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(class-transformer@0.5.1)(reflect-metadata@0.2.2)': + '@nestjs/swagger@11.4.3(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(reflect-metadata@0.2.2)(typescript@6.0.3)': dependencies: '@microsoft/tsdoc': 0.16.0 - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/mapped-types': 2.1.1(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(class-transformer@0.5.1)(reflect-metadata@0.2.2) + '@nestjs/common': 11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.21(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.21)(@nestjs/websockets@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/mapped-types': 2.1.1(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2) js-yaml: 4.1.1 lodash: 4.18.1 path-to-regexp: 8.4.2 reflect-metadata: 0.2.2 - swagger-ui-dist: 5.32.4 - optionalDependencies: - class-transformer: 0.5.1 + swagger-ui-dist: 5.32.6 + typescript: 6.0.3 - '@nestjs/testing@11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/platform-express@11.1.19)': + '@nestjs/testing@11.1.21(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(@nestjs/platform-express@11.1.21)': dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.21(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.21)(@nestjs/websockets@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2) tslib: 2.8.1 optionalDependencies: - '@nestjs/platform-express': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19) + '@nestjs/platform-express': 11.1.21(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21) - '@nestjs/websockets@11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/platform-socket.io@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2)': + '@nestjs/websockets@11.1.21(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(@nestjs/platform-socket.io@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2)': dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.21(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.21)(@nestjs/websockets@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2) iterare: 1.2.1 object-hash: 3.0.0 reflect-metadata: 0.2.2 rxjs: 7.8.2 tslib: 2.8.1 optionalDependencies: - '@nestjs/platform-socket.io': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.19)(rxjs@7.8.2) + '@nestjs/platform-socket.io': 11.1.21(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.21)(rxjs@7.8.2) + + '@noble/hashes@1.4.0': {} '@noble/hashes@1.8.0': {} @@ -16148,318 +16603,301 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.20.1 - '@npmcli/agent@4.0.0': - dependencies: - agent-base: 7.1.4 - http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.6 - lru-cache: 11.3.5 - socks-proxy-agent: 8.0.5 - transitivePeerDependencies: - - supports-color - - '@npmcli/fs@5.0.0': - dependencies: - semver: 7.7.4 - '@nuxt/opencollective@0.4.1': dependencies: consola: 3.4.2 '@oazapfts/runtime@1.2.0': {} - '@opentelemetry/api-logs@0.215.0': + '@opentelemetry/api-logs@0.218.0': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/api@1.9.0': {} - '@opentelemetry/api@1.9.1': {} - '@opentelemetry/configuration@0.215.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/configuration@0.218.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.7.0(@opentelemetry/api@1.9.1) - yaml: 2.8.3 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + yaml: 2.9.0 - '@opentelemetry/context-async-hooks@2.7.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/context-async-hooks@2.7.1(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/core@2.7.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/core@2.7.1(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/semantic-conventions': 1.40.0 + '@opentelemetry/semantic-conventions': 1.41.1 - '@opentelemetry/exporter-logs-otlp-grpc@0.215.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/exporter-logs-otlp-grpc@0.218.0(@opentelemetry/api@1.9.1)': dependencies: '@grpc/grpc-js': 1.14.3 '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.7.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-exporter-base': 0.215.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-grpc-exporter-base': 0.215.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-transformer': 0.215.0(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-logs': 0.215.0(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-grpc-exporter-base': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-logs': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-logs-otlp-http@0.215.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/exporter-logs-otlp-http@0.218.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/api-logs': 0.215.0 - '@opentelemetry/core': 2.7.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-exporter-base': 0.215.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-transformer': 0.215.0(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-logs': 0.215.0(@opentelemetry/api@1.9.1) + '@opentelemetry/api-logs': 0.218.0 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-logs': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-logs-otlp-proto@0.215.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/exporter-logs-otlp-proto@0.218.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/api-logs': 0.215.0 - '@opentelemetry/core': 2.7.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-exporter-base': 0.215.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-transformer': 0.215.0(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 2.7.0(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-logs': 0.215.0(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-trace-base': 2.7.0(@opentelemetry/api@1.9.1) + '@opentelemetry/api-logs': 0.218.0 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-logs': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-metrics-otlp-grpc@0.215.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/exporter-metrics-otlp-grpc@0.218.0(@opentelemetry/api@1.9.1)': dependencies: '@grpc/grpc-js': 1.14.3 '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.7.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-metrics-otlp-http': 0.215.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-exporter-base': 0.215.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-grpc-exporter-base': 0.215.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-transformer': 0.215.0(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 2.7.0(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-metrics': 2.7.0(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-metrics-otlp-http': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-grpc-exporter-base': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-metrics-otlp-http@0.215.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/exporter-metrics-otlp-http@0.218.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.7.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-exporter-base': 0.215.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-transformer': 0.215.0(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 2.7.0(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-metrics': 2.7.0(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-metrics-otlp-proto@0.215.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/exporter-metrics-otlp-proto@0.218.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.7.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-metrics-otlp-http': 0.215.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-exporter-base': 0.215.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-transformer': 0.215.0(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 2.7.0(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-metrics': 2.7.0(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-metrics-otlp-http': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-prometheus@0.215.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/exporter-prometheus@0.218.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.7.0(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 2.7.0(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-metrics': 2.7.0(@opentelemetry/api@1.9.1) - '@opentelemetry/semantic-conventions': 1.40.0 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 - '@opentelemetry/exporter-trace-otlp-grpc@0.215.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/exporter-trace-otlp-grpc@0.218.0(@opentelemetry/api@1.9.1)': dependencies: '@grpc/grpc-js': 1.14.3 '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.7.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-exporter-base': 0.215.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-grpc-exporter-base': 0.215.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-transformer': 0.215.0(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 2.7.0(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-trace-base': 2.7.0(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-grpc-exporter-base': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-trace-otlp-http@0.215.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/exporter-trace-otlp-http@0.218.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.7.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-exporter-base': 0.215.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-transformer': 0.215.0(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 2.7.0(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-trace-base': 2.7.0(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-trace-otlp-proto@0.215.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/exporter-trace-otlp-proto@0.218.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.7.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-exporter-base': 0.215.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-transformer': 0.215.0(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 2.7.0(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-trace-base': 2.7.0(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-zipkin@2.7.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/exporter-zipkin@2.7.1(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.7.0(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 2.7.0(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-trace-base': 2.7.0(@opentelemetry/api@1.9.1) - '@opentelemetry/semantic-conventions': 1.40.0 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 - '@opentelemetry/host-metrics@0.36.2(@opentelemetry/api@1.9.1)': + '@opentelemetry/host-metrics@0.38.3(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - systeminformation: 5.23.8 + systeminformation: 5.31.6 - '@opentelemetry/instrumentation-http@0.215.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/instrumentation-http@0.218.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.7.0(@opentelemetry/api@1.9.1) - '@opentelemetry/instrumentation': 0.215.0(@opentelemetry/api@1.9.1) - '@opentelemetry/semantic-conventions': 1.40.0 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 forwarded-parse: 2.1.2 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-ioredis@0.63.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/instrumentation-ioredis@0.66.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/instrumentation': 0.215.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.218.0(@opentelemetry/api@1.9.1) '@opentelemetry/redis-common': 0.38.3 - '@opentelemetry/semantic-conventions': 1.40.0 + '@opentelemetry/semantic-conventions': 1.41.1 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-nestjs-core@0.61.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/instrumentation-nestjs-core@0.64.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/instrumentation': 0.215.0(@opentelemetry/api@1.9.1) - '@opentelemetry/semantic-conventions': 1.40.0 + '@opentelemetry/instrumentation': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-pg@0.67.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/instrumentation-pg@0.70.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.7.0(@opentelemetry/api@1.9.1) - '@opentelemetry/instrumentation': 0.215.0(@opentelemetry/api@1.9.1) - '@opentelemetry/semantic-conventions': 1.40.0 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 '@opentelemetry/sql-common': 0.41.2(@opentelemetry/api@1.9.1) '@types/pg': 8.15.6 '@types/pg-pool': 2.0.7 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation@0.215.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/instrumentation@0.218.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/api-logs': 0.215.0 - import-in-the-middle: 3.0.0 + '@opentelemetry/api-logs': 0.218.0 + import-in-the-middle: 3.0.1 require-in-the-middle: 8.0.1 transitivePeerDependencies: - supports-color - '@opentelemetry/otlp-exporter-base@0.215.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/otlp-exporter-base@0.218.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.7.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-transformer': 0.215.0(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-grpc-exporter-base@0.215.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/otlp-grpc-exporter-base@0.218.0(@opentelemetry/api@1.9.1)': dependencies: '@grpc/grpc-js': 1.14.3 '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.7.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-exporter-base': 0.215.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-transformer': 0.215.0(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-transformer@0.215.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/otlp-transformer@0.218.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/api-logs': 0.215.0 - '@opentelemetry/core': 2.7.0(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 2.7.0(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-logs': 0.215.0(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-metrics': 2.7.0(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-trace-base': 2.7.0(@opentelemetry/api@1.9.1) - protobufjs: 8.0.1 + '@opentelemetry/api-logs': 0.218.0 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-logs': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/propagator-b3@2.7.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/propagator-b3@2.7.1(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.7.0(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/propagator-jaeger@2.7.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/propagator-jaeger@2.7.1(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.7.0(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/redis-common@0.38.3': {} - '@opentelemetry/resources@2.7.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/resources@2.7.1(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.7.0(@opentelemetry/api@1.9.1) - '@opentelemetry/semantic-conventions': 1.40.0 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 - '@opentelemetry/sdk-logs@0.215.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/sdk-logs@0.218.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/api-logs': 0.215.0 - '@opentelemetry/core': 2.7.0(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 2.7.0(@opentelemetry/api@1.9.1) - '@opentelemetry/semantic-conventions': 1.40.0 + '@opentelemetry/api-logs': 0.218.0 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 - '@opentelemetry/sdk-metrics@2.7.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/sdk-metrics@2.7.1(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.7.0(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 2.7.0(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-node@0.215.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/sdk-node@0.218.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/api-logs': 0.215.0 - '@opentelemetry/configuration': 0.215.0(@opentelemetry/api@1.9.1) - '@opentelemetry/context-async-hooks': 2.7.0(@opentelemetry/api@1.9.1) - '@opentelemetry/core': 2.7.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-logs-otlp-grpc': 0.215.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-logs-otlp-http': 0.215.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-logs-otlp-proto': 0.215.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-metrics-otlp-grpc': 0.215.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-metrics-otlp-http': 0.215.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-metrics-otlp-proto': 0.215.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-prometheus': 0.215.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-trace-otlp-grpc': 0.215.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-trace-otlp-http': 0.215.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-trace-otlp-proto': 0.215.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-zipkin': 2.7.0(@opentelemetry/api@1.9.1) - '@opentelemetry/instrumentation': 0.215.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-exporter-base': 0.215.0(@opentelemetry/api@1.9.1) - '@opentelemetry/propagator-b3': 2.7.0(@opentelemetry/api@1.9.1) - '@opentelemetry/propagator-jaeger': 2.7.0(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 2.7.0(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-logs': 0.215.0(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-metrics': 2.7.0(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-trace-base': 2.7.0(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-trace-node': 2.7.0(@opentelemetry/api@1.9.1) - '@opentelemetry/semantic-conventions': 1.40.0 + '@opentelemetry/api-logs': 0.218.0 + '@opentelemetry/configuration': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/context-async-hooks': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-logs-otlp-grpc': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-logs-otlp-http': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-logs-otlp-proto': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-metrics-otlp-grpc': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-metrics-otlp-http': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-metrics-otlp-proto': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-prometheus': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-trace-otlp-grpc': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-trace-otlp-http': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-trace-otlp-proto': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-zipkin': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/propagator-b3': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/propagator-jaeger': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-logs': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-node': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 transitivePeerDependencies: - supports-color - '@opentelemetry/sdk-trace-base@2.7.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.7.0(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 2.7.0(@opentelemetry/api@1.9.1) - '@opentelemetry/semantic-conventions': 1.40.0 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 - '@opentelemetry/sdk-trace-node@2.7.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/sdk-trace-node@2.7.1(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/context-async-hooks': 2.7.0(@opentelemetry/api@1.9.1) - '@opentelemetry/core': 2.7.0(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-trace-base': 2.7.0(@opentelemetry/api@1.9.1) + '@opentelemetry/context-async-hooks': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/semantic-conventions@1.40.0': {} + '@opentelemetry/semantic-conventions@1.41.1': {} '@opentelemetry/sql-common@0.41.2(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.7.0(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - '@oxc-project/types@0.127.0': {} + '@oxc-project/types@0.130.0': {} '@paralleldrive/cuid2@2.3.1': dependencies: @@ -16526,6 +16964,100 @@ snapshots: '@parcel/watcher-win32-x64': 2.5.6 optional: true + '@peculiar/asn1-cms@2.7.0': + dependencies: + '@peculiar/asn1-schema': 2.7.0 + '@peculiar/asn1-x509': 2.7.0 + '@peculiar/asn1-x509-attr': 2.7.0 + asn1js: 3.0.10 + tslib: 2.8.1 + + '@peculiar/asn1-csr@2.7.0': + dependencies: + '@peculiar/asn1-schema': 2.7.0 + '@peculiar/asn1-x509': 2.7.0 + asn1js: 3.0.10 + tslib: 2.8.1 + + '@peculiar/asn1-ecc@2.7.0': + dependencies: + '@peculiar/asn1-schema': 2.7.0 + '@peculiar/asn1-x509': 2.7.0 + asn1js: 3.0.10 + tslib: 2.8.1 + + '@peculiar/asn1-pfx@2.7.0': + dependencies: + '@peculiar/asn1-cms': 2.7.0 + '@peculiar/asn1-pkcs8': 2.7.0 + '@peculiar/asn1-rsa': 2.7.0 + '@peculiar/asn1-schema': 2.7.0 + asn1js: 3.0.10 + tslib: 2.8.1 + + '@peculiar/asn1-pkcs8@2.7.0': + dependencies: + '@peculiar/asn1-schema': 2.7.0 + '@peculiar/asn1-x509': 2.7.0 + asn1js: 3.0.10 + tslib: 2.8.1 + + '@peculiar/asn1-pkcs9@2.7.0': + dependencies: + '@peculiar/asn1-cms': 2.7.0 + '@peculiar/asn1-pfx': 2.7.0 + '@peculiar/asn1-pkcs8': 2.7.0 + '@peculiar/asn1-schema': 2.7.0 + '@peculiar/asn1-x509': 2.7.0 + '@peculiar/asn1-x509-attr': 2.7.0 + asn1js: 3.0.10 + tslib: 2.8.1 + + '@peculiar/asn1-rsa@2.7.0': + dependencies: + '@peculiar/asn1-schema': 2.7.0 + '@peculiar/asn1-x509': 2.7.0 + asn1js: 3.0.10 + tslib: 2.8.1 + + '@peculiar/asn1-schema@2.7.0': + dependencies: + '@peculiar/utils': 2.0.3 + asn1js: 3.0.10 + tslib: 2.8.1 + + '@peculiar/asn1-x509-attr@2.7.0': + dependencies: + '@peculiar/asn1-schema': 2.7.0 + '@peculiar/asn1-x509': 2.7.0 + asn1js: 3.0.10 + tslib: 2.8.1 + + '@peculiar/asn1-x509@2.7.0': + dependencies: + '@peculiar/asn1-schema': 2.7.0 + '@peculiar/utils': 2.0.3 + asn1js: 3.0.10 + tslib: 2.8.1 + + '@peculiar/utils@2.0.3': + dependencies: + tslib: 2.8.1 + + '@peculiar/x509@1.14.3': + dependencies: + '@peculiar/asn1-cms': 2.7.0 + '@peculiar/asn1-csr': 2.7.0 + '@peculiar/asn1-ecc': 2.7.0 + '@peculiar/asn1-pkcs9': 2.7.0 + '@peculiar/asn1-rsa': 2.7.0 + '@peculiar/asn1-schema': 2.7.0 + '@peculiar/asn1-x509': 2.7.0 + pvtsutils: 1.3.6 + reflect-metadata: 0.2.2 + tslib: 2.8.1 + tsyringe: 4.10.0 + '@photo-sphere-viewer/core@5.14.1': dependencies: three: 0.179.1 @@ -16534,7 +17066,7 @@ snapshots: dependencies: '@photo-sphere-viewer/core': 5.14.1 '@photo-sphere-viewer/video-plugin': 5.14.1(@photo-sphere-viewer/core@5.14.1) - three: 0.182.0 + three: 0.184.0 '@photo-sphere-viewer/markers-plugin@5.14.1(@photo-sphere-viewer/core@5.14.1)': dependencies: @@ -16552,7 +17084,7 @@ snapshots: '@photo-sphere-viewer/video-plugin@5.14.1(@photo-sphere-viewer/core@5.14.1)': dependencies: '@photo-sphere-viewer/core': 5.14.1 - three: 0.182.0 + three: 0.184.0 '@photostructure/tz-lookup@11.5.0': {} @@ -16561,9 +17093,9 @@ snapshots: '@pkgr/core@0.2.9': {} - '@playwright/test@1.59.1': + '@playwright/test@1.60.0': dependencies: - playwright: 1.59.1 + playwright: 1.60.0 '@pnpm/config.env-replace@1.1.0': {} @@ -16571,7 +17103,7 @@ snapshots: dependencies: graceful-fs: 4.2.10 - '@pnpm/npm-conf@2.3.1': + '@pnpm/npm-conf@3.0.2': dependencies: '@pnpm/config.env-replace': 1.1.0 '@pnpm/network.ca-file': 1.0.2 @@ -16583,294 +17115,293 @@ snapshots: '@protobufjs/base64@1.1.2': {} - '@protobufjs/codegen@2.0.4': {} + '@protobufjs/codegen@2.0.5': {} '@protobufjs/eventemitter@1.1.0': {} - '@protobufjs/fetch@1.1.0': + '@protobufjs/fetch@1.1.1': dependencies: '@protobufjs/aspromise': 1.1.2 - '@protobufjs/inquire': 1.1.0 '@protobufjs/float@1.0.2': {} - '@protobufjs/inquire@1.1.0': {} + '@protobufjs/inquire@1.1.2': {} '@protobufjs/path@1.1.2': {} '@protobufjs/pool@1.1.0': {} - '@protobufjs/utf8@1.1.0': {} + '@protobufjs/utf8@1.1.1': {} - '@react-email/body@0.3.0(react@19.2.5)': + '@react-email/body@0.3.0(react@19.2.6)': dependencies: - react: 19.2.5 + react: 19.2.6 - '@react-email/button@0.2.1(react@19.2.5)': + '@react-email/button@0.2.1(react@19.2.6)': dependencies: - react: 19.2.5 + react: 19.2.6 - '@react-email/code-block@0.2.1(react@19.2.5)': + '@react-email/code-block@0.2.1(react@19.2.6)': dependencies: prismjs: 1.30.0 - react: 19.2.5 + react: 19.2.6 - '@react-email/code-inline@0.0.6(react@19.2.5)': + '@react-email/code-inline@0.0.6(react@19.2.6)': dependencies: - react: 19.2.5 + react: 19.2.6 - '@react-email/column@0.0.14(react@19.2.5)': + '@react-email/column@0.0.14(react@19.2.6)': dependencies: - react: 19.2.5 + react: 19.2.6 - '@react-email/components@1.0.12(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@react-email/components@1.0.12(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@react-email/body': 0.3.0(react@19.2.5) - '@react-email/button': 0.2.1(react@19.2.5) - '@react-email/code-block': 0.2.1(react@19.2.5) - '@react-email/code-inline': 0.0.6(react@19.2.5) - '@react-email/column': 0.0.14(react@19.2.5) - '@react-email/container': 0.0.16(react@19.2.5) - '@react-email/font': 0.0.10(react@19.2.5) - '@react-email/head': 0.0.13(react@19.2.5) - '@react-email/heading': 0.0.16(react@19.2.5) - '@react-email/hr': 0.0.12(react@19.2.5) - '@react-email/html': 0.0.12(react@19.2.5) - '@react-email/img': 0.0.12(react@19.2.5) - '@react-email/link': 0.0.13(react@19.2.5) - '@react-email/markdown': 0.0.18(react@19.2.5) - '@react-email/preview': 0.0.14(react@19.2.5) - '@react-email/render': 2.0.6(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-email/row': 0.0.13(react@19.2.5) - '@react-email/section': 0.0.17(react@19.2.5) - '@react-email/tailwind': 2.0.7(@react-email/body@0.3.0(react@19.2.5))(@react-email/button@0.2.1(react@19.2.5))(@react-email/code-block@0.2.1(react@19.2.5))(@react-email/code-inline@0.0.6(react@19.2.5))(@react-email/container@0.0.16(react@19.2.5))(@react-email/heading@0.0.16(react@19.2.5))(@react-email/hr@0.0.12(react@19.2.5))(@react-email/img@0.0.12(react@19.2.5))(@react-email/link@0.0.13(react@19.2.5))(@react-email/preview@0.0.14(react@19.2.5))(@react-email/text@0.1.6(react@19.2.5))(react@19.2.5) - '@react-email/text': 0.1.6(react@19.2.5) - react: 19.2.5 + '@react-email/body': 0.3.0(react@19.2.6) + '@react-email/button': 0.2.1(react@19.2.6) + '@react-email/code-block': 0.2.1(react@19.2.6) + '@react-email/code-inline': 0.0.6(react@19.2.6) + '@react-email/column': 0.0.14(react@19.2.6) + '@react-email/container': 0.0.16(react@19.2.6) + '@react-email/font': 0.0.10(react@19.2.6) + '@react-email/head': 0.0.13(react@19.2.6) + '@react-email/heading': 0.0.16(react@19.2.6) + '@react-email/hr': 0.0.12(react@19.2.6) + '@react-email/html': 0.0.12(react@19.2.6) + '@react-email/img': 0.0.12(react@19.2.6) + '@react-email/link': 0.0.13(react@19.2.6) + '@react-email/markdown': 0.0.18(react@19.2.6) + '@react-email/preview': 0.0.14(react@19.2.6) + '@react-email/render': 2.0.6(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@react-email/row': 0.0.13(react@19.2.6) + '@react-email/section': 0.0.17(react@19.2.6) + '@react-email/tailwind': 2.0.7(@react-email/body@0.3.0(react@19.2.6))(@react-email/button@0.2.1(react@19.2.6))(@react-email/code-block@0.2.1(react@19.2.6))(@react-email/code-inline@0.0.6(react@19.2.6))(@react-email/container@0.0.16(react@19.2.6))(@react-email/heading@0.0.16(react@19.2.6))(@react-email/hr@0.0.12(react@19.2.6))(@react-email/img@0.0.12(react@19.2.6))(@react-email/link@0.0.13(react@19.2.6))(@react-email/preview@0.0.14(react@19.2.6))(@react-email/text@0.1.6(react@19.2.6))(react@19.2.6) + '@react-email/text': 0.1.6(react@19.2.6) + react: 19.2.6 transitivePeerDependencies: - react-dom - '@react-email/container@0.0.16(react@19.2.5)': + '@react-email/container@0.0.16(react@19.2.6)': dependencies: - react: 19.2.5 + react: 19.2.6 - '@react-email/font@0.0.10(react@19.2.5)': + '@react-email/font@0.0.10(react@19.2.6)': dependencies: - react: 19.2.5 + react: 19.2.6 - '@react-email/head@0.0.13(react@19.2.5)': + '@react-email/head@0.0.13(react@19.2.6)': dependencies: - react: 19.2.5 + react: 19.2.6 - '@react-email/heading@0.0.16(react@19.2.5)': + '@react-email/heading@0.0.16(react@19.2.6)': dependencies: - react: 19.2.5 + react: 19.2.6 - '@react-email/hr@0.0.12(react@19.2.5)': + '@react-email/hr@0.0.12(react@19.2.6)': dependencies: - react: 19.2.5 + react: 19.2.6 - '@react-email/html@0.0.12(react@19.2.5)': + '@react-email/html@0.0.12(react@19.2.6)': dependencies: - react: 19.2.5 + react: 19.2.6 - '@react-email/img@0.0.12(react@19.2.5)': + '@react-email/img@0.0.12(react@19.2.6)': dependencies: - react: 19.2.5 + react: 19.2.6 - '@react-email/link@0.0.13(react@19.2.5)': + '@react-email/link@0.0.13(react@19.2.6)': dependencies: - react: 19.2.5 + react: 19.2.6 - '@react-email/markdown@0.0.18(react@19.2.5)': + '@react-email/markdown@0.0.18(react@19.2.6)': dependencies: marked: 15.0.12 - react: 19.2.5 + react: 19.2.6 - '@react-email/preview@0.0.14(react@19.2.5)': + '@react-email/preview@0.0.14(react@19.2.6)': dependencies: - react: 19.2.5 + react: 19.2.6 - '@react-email/render@2.0.6(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@react-email/render@2.0.6(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: html-to-text: 9.0.5 prettier: 3.8.3 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) - '@react-email/render@2.0.7(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@react-email/render@2.0.8(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: html-to-text: 9.0.5 prettier: 3.8.3 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) - '@react-email/row@0.0.13(react@19.2.5)': + '@react-email/row@0.0.13(react@19.2.6)': dependencies: - react: 19.2.5 + react: 19.2.6 - '@react-email/section@0.0.17(react@19.2.5)': + '@react-email/section@0.0.17(react@19.2.6)': dependencies: - react: 19.2.5 + react: 19.2.6 - '@react-email/tailwind@2.0.7(@react-email/body@0.3.0(react@19.2.5))(@react-email/button@0.2.1(react@19.2.5))(@react-email/code-block@0.2.1(react@19.2.5))(@react-email/code-inline@0.0.6(react@19.2.5))(@react-email/container@0.0.16(react@19.2.5))(@react-email/heading@0.0.16(react@19.2.5))(@react-email/hr@0.0.12(react@19.2.5))(@react-email/img@0.0.12(react@19.2.5))(@react-email/link@0.0.13(react@19.2.5))(@react-email/preview@0.0.14(react@19.2.5))(@react-email/text@0.1.6(react@19.2.5))(react@19.2.5)': + '@react-email/tailwind@2.0.7(@react-email/body@0.3.0(react@19.2.6))(@react-email/button@0.2.1(react@19.2.6))(@react-email/code-block@0.2.1(react@19.2.6))(@react-email/code-inline@0.0.6(react@19.2.6))(@react-email/container@0.0.16(react@19.2.6))(@react-email/heading@0.0.16(react@19.2.6))(@react-email/hr@0.0.12(react@19.2.6))(@react-email/img@0.0.12(react@19.2.6))(@react-email/link@0.0.13(react@19.2.6))(@react-email/preview@0.0.14(react@19.2.6))(@react-email/text@0.1.6(react@19.2.6))(react@19.2.6)': dependencies: - '@react-email/text': 0.1.6(react@19.2.5) - react: 19.2.5 - tailwindcss: 4.2.4 + '@react-email/text': 0.1.6(react@19.2.6) + react: 19.2.6 + tailwindcss: 4.3.0 optionalDependencies: - '@react-email/body': 0.3.0(react@19.2.5) - '@react-email/button': 0.2.1(react@19.2.5) - '@react-email/code-block': 0.2.1(react@19.2.5) - '@react-email/code-inline': 0.0.6(react@19.2.5) - '@react-email/container': 0.0.16(react@19.2.5) - '@react-email/heading': 0.0.16(react@19.2.5) - '@react-email/hr': 0.0.12(react@19.2.5) - '@react-email/img': 0.0.12(react@19.2.5) - '@react-email/link': 0.0.13(react@19.2.5) - '@react-email/preview': 0.0.14(react@19.2.5) + '@react-email/body': 0.3.0(react@19.2.6) + '@react-email/button': 0.2.1(react@19.2.6) + '@react-email/code-block': 0.2.1(react@19.2.6) + '@react-email/code-inline': 0.0.6(react@19.2.6) + '@react-email/container': 0.0.16(react@19.2.6) + '@react-email/heading': 0.0.16(react@19.2.6) + '@react-email/hr': 0.0.12(react@19.2.6) + '@react-email/img': 0.0.12(react@19.2.6) + '@react-email/link': 0.0.13(react@19.2.6) + '@react-email/preview': 0.0.14(react@19.2.6) - '@react-email/text@0.1.6(react@19.2.5)': + '@react-email/text@0.1.6(react@19.2.6)': dependencies: - react: 19.2.5 + react: 19.2.6 - '@replit/codemirror-indentation-markers@6.5.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.41.1)': + '@replit/codemirror-indentation-markers@6.5.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.43.0)': dependencies: '@codemirror/language': 6.12.3 '@codemirror/state': 6.6.0 - '@codemirror/view': 6.41.1 + '@codemirror/view': 6.43.0 - '@rolldown/binding-android-arm64@1.0.0-rc.17': + '@rolldown/binding-android-arm64@1.0.1': optional: true - '@rolldown/binding-darwin-arm64@1.0.0-rc.17': + '@rolldown/binding-darwin-arm64@1.0.1': optional: true - '@rolldown/binding-darwin-x64@1.0.0-rc.17': + '@rolldown/binding-darwin-x64@1.0.1': optional: true - '@rolldown/binding-freebsd-x64@1.0.0-rc.17': + '@rolldown/binding-freebsd-x64@1.0.1': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.17': + '@rolldown/binding-linux-arm-gnueabihf@1.0.1': optional: true - '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.17': + '@rolldown/binding-linux-arm64-gnu@1.0.1': optional: true - '@rolldown/binding-linux-arm64-musl@1.0.0-rc.17': + '@rolldown/binding-linux-arm64-musl@1.0.1': optional: true - '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.17': + '@rolldown/binding-linux-ppc64-gnu@1.0.1': optional: true - '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.17': + '@rolldown/binding-linux-s390x-gnu@1.0.1': optional: true - '@rolldown/binding-linux-x64-gnu@1.0.0-rc.17': + '@rolldown/binding-linux-x64-gnu@1.0.1': optional: true - '@rolldown/binding-linux-x64-musl@1.0.0-rc.17': + '@rolldown/binding-linux-x64-musl@1.0.1': optional: true - '@rolldown/binding-openharmony-arm64@1.0.0-rc.17': + '@rolldown/binding-openharmony-arm64@1.0.1': optional: true - '@rolldown/binding-wasm32-wasi@1.0.0-rc.17': + '@rolldown/binding-wasm32-wasi@1.0.1': dependencies: '@emnapi/core': 1.10.0 '@emnapi/runtime': 1.10.0 '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) optional: true - '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.17': + '@rolldown/binding-win32-arm64-msvc@1.0.1': optional: true - '@rolldown/binding-win32-x64-msvc@1.0.0-rc.17': + '@rolldown/binding-win32-x64-msvc@1.0.1': optional: true - '@rolldown/pluginutils@1.0.0-rc.17': {} + '@rolldown/pluginutils@1.0.1': {} - '@rollup/pluginutils@5.3.0(rollup@4.55.1)': + '@rollup/pluginutils@5.3.0(rollup@4.60.4)': dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 estree-walker: 2.0.2 picomatch: 4.0.4 optionalDependencies: - rollup: 4.55.1 + rollup: 4.60.4 - '@rollup/rollup-android-arm-eabi@4.55.1': + '@rollup/rollup-android-arm-eabi@4.60.4': optional: true - '@rollup/rollup-android-arm64@4.55.1': + '@rollup/rollup-android-arm64@4.60.4': optional: true - '@rollup/rollup-darwin-arm64@4.55.1': + '@rollup/rollup-darwin-arm64@4.60.4': optional: true - '@rollup/rollup-darwin-x64@4.55.1': + '@rollup/rollup-darwin-x64@4.60.4': optional: true - '@rollup/rollup-freebsd-arm64@4.55.1': + '@rollup/rollup-freebsd-arm64@4.60.4': optional: true - '@rollup/rollup-freebsd-x64@4.55.1': + '@rollup/rollup-freebsd-x64@4.60.4': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.55.1': + '@rollup/rollup-linux-arm-gnueabihf@4.60.4': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.55.1': + '@rollup/rollup-linux-arm-musleabihf@4.60.4': optional: true - '@rollup/rollup-linux-arm64-gnu@4.55.1': + '@rollup/rollup-linux-arm64-gnu@4.60.4': optional: true - '@rollup/rollup-linux-arm64-musl@4.55.1': + '@rollup/rollup-linux-arm64-musl@4.60.4': optional: true - '@rollup/rollup-linux-loong64-gnu@4.55.1': + '@rollup/rollup-linux-loong64-gnu@4.60.4': optional: true - '@rollup/rollup-linux-loong64-musl@4.55.1': + '@rollup/rollup-linux-loong64-musl@4.60.4': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.55.1': + '@rollup/rollup-linux-ppc64-gnu@4.60.4': optional: true - '@rollup/rollup-linux-ppc64-musl@4.55.1': + '@rollup/rollup-linux-ppc64-musl@4.60.4': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.55.1': + '@rollup/rollup-linux-riscv64-gnu@4.60.4': optional: true - '@rollup/rollup-linux-riscv64-musl@4.55.1': + '@rollup/rollup-linux-riscv64-musl@4.60.4': optional: true - '@rollup/rollup-linux-s390x-gnu@4.55.1': + '@rollup/rollup-linux-s390x-gnu@4.60.4': optional: true - '@rollup/rollup-linux-x64-gnu@4.55.1': + '@rollup/rollup-linux-x64-gnu@4.60.4': optional: true - '@rollup/rollup-linux-x64-musl@4.55.1': + '@rollup/rollup-linux-x64-musl@4.60.4': optional: true - '@rollup/rollup-openbsd-x64@4.55.1': + '@rollup/rollup-openbsd-x64@4.60.4': optional: true - '@rollup/rollup-openharmony-arm64@4.55.1': + '@rollup/rollup-openharmony-arm64@4.60.4': optional: true - '@rollup/rollup-win32-arm64-msvc@4.55.1': + '@rollup/rollup-win32-arm64-msvc@4.60.4': optional: true - '@rollup/rollup-win32-ia32-msvc@4.55.1': + '@rollup/rollup-win32-ia32-msvc@4.60.4': optional: true - '@rollup/rollup-win32-x64-gnu@4.55.1': + '@rollup/rollup-win32-x64-gnu@4.60.4': optional: true - '@rollup/rollup-win32-x64-msvc@4.55.1': + '@rollup/rollup-win32-x64-msvc@4.60.4': optional: true '@scarf/scarf@1.4.0': {} @@ -16888,19 +17419,19 @@ snapshots: '@sideway/pinpoint@2.0.0': {} - '@sinclair/typebox@0.27.8': {} + '@sinclair/typebox@0.27.10': {} '@sindresorhus/is@4.6.0': {} '@sindresorhus/is@5.6.0': {} - '@slorber/react-helmet-async@1.3.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@slorber/react-helmet-async@1.3.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 invariant: 2.2.4 prop-types: 15.8.1 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) react-fast-compare: 3.2.2 shallowequal: 1.1.0 @@ -16912,11 +17443,11 @@ snapshots: '@socket.io/component-emitter@3.1.2': {} - '@socket.io/redis-adapter@8.3.0(socket.io-adapter@2.5.6)': + '@socket.io/redis-adapter@8.3.0(socket.io-adapter@2.5.7)': dependencies: debug: 4.3.7 notepack.io: 3.0.1 - socket.io-adapter: 2.5.6 + socket.io-adapter: 2.5.7 uid2: 1.0.0 transitivePeerDependencies: - supports-color @@ -16925,106 +17456,105 @@ snapshots: '@standard-schema/spec@1.1.0': {} - '@sveltejs/acorn-typescript@1.0.9(acorn@8.16.0)': + '@sveltejs/acorn-typescript@1.0.10(acorn@8.16.0)': dependencies: acorn: 8.16.0 - '@sveltejs/adapter-static@3.0.10(@sveltejs/kit@2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))': + '@sveltejs/adapter-static@3.0.10(@sveltejs/kit@2.60.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.55.8(@typescript-eslint/types@8.59.4))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4))(typescript@6.0.3)(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))': dependencies: - '@sveltejs/kit': 2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + '@sveltejs/kit': 2.60.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.55.8(@typescript-eslint/types@8.59.4))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4))(typescript@6.0.3)(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)) - '@sveltejs/enhanced-img@0.10.4(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(rollup@4.55.1)(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': + '@sveltejs/enhanced-img@0.10.4(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.55.8(@typescript-eslint/types@8.59.4))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(rollup@4.60.4)(svelte@5.55.8(@typescript-eslint/types@8.59.4))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0))': dependencies: - '@sveltejs/vite-plugin-svelte': 7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + '@sveltejs/vite-plugin-svelte': 7.1.2(svelte@5.55.8(@typescript-eslint/types@8.59.4))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)) magic-string: 0.30.21 sharp: 0.34.5 - svelte: 5.55.2 - svelte-parse-markup: 0.1.5(svelte@5.55.2) - vite: 8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) - vite-imagetools: 9.0.3(rollup@4.55.1) + svelte: 5.55.8(@typescript-eslint/types@8.59.4) + svelte-parse-markup: 0.1.5(svelte@5.55.8(@typescript-eslint/types@8.59.4)) + vite: 8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0) + vite-imagetools: 9.0.3(rollup@4.60.4) zimmerframe: 1.1.4 transitivePeerDependencies: - rollup - - supports-color - '@sveltejs/kit@2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': + '@sveltejs/kit@2.60.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.55.8(@typescript-eslint/types@8.59.4))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4))(typescript@6.0.3)(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0))': dependencies: '@standard-schema/spec': 1.1.0 - '@sveltejs/acorn-typescript': 1.0.9(acorn@8.16.0) - '@sveltejs/vite-plugin-svelte': 7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + '@sveltejs/acorn-typescript': 1.0.10(acorn@8.16.0) + '@sveltejs/vite-plugin-svelte': 7.1.2(svelte@5.55.8(@typescript-eslint/types@8.59.4))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)) '@types/cookie': 0.6.0 acorn: 8.16.0 cookie: 0.6.0 - devalue: 5.7.1 + devalue: 5.8.1 esm-env: 1.2.2 kleur: 4.1.5 magic-string: 0.30.21 mrmime: 2.0.1 set-cookie-parser: 3.1.0 sirv: 3.0.2 - svelte: 5.55.2 - vite: 8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + svelte: 5.55.8(@typescript-eslint/types@8.59.4) + vite: 8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0) optionalDependencies: '@opentelemetry/api': 1.9.1 typescript: 6.0.3 - '@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': + '@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.55.8(@typescript-eslint/types@8.59.4))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0))': dependencies: deepmerge: 4.3.1 magic-string: 0.30.21 obug: 2.1.1 - svelte: 5.55.2 - vite: 8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) - vitefu: 1.1.2(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + svelte: 5.55.8(@typescript-eslint/types@8.59.4) + vite: 8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0) + vitefu: 1.1.3(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)) - '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.28.5)': + '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 - '@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.28.5)': + '@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 - '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.28.5)': + '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 - '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.28.5)': + '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 - '@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.28.5)': + '@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 - '@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.28.5)': + '@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 - '@svgr/babel-plugin-transform-react-native-svg@8.1.0(@babel/core@7.28.5)': + '@svgr/babel-plugin-transform-react-native-svg@8.1.0(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 - '@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.28.5)': + '@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 - '@svgr/babel-preset@8.1.0(@babel/core@7.28.5)': + '@svgr/babel-preset@8.1.0(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@svgr/babel-plugin-add-jsx-attribute': 8.0.0(@babel/core@7.28.5) - '@svgr/babel-plugin-remove-jsx-attribute': 8.0.0(@babel/core@7.28.5) - '@svgr/babel-plugin-remove-jsx-empty-expression': 8.0.0(@babel/core@7.28.5) - '@svgr/babel-plugin-replace-jsx-attribute-value': 8.0.0(@babel/core@7.28.5) - '@svgr/babel-plugin-svg-dynamic-title': 8.0.0(@babel/core@7.28.5) - '@svgr/babel-plugin-svg-em-dimensions': 8.0.0(@babel/core@7.28.5) - '@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.28.5) - '@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.28.5) + '@babel/core': 7.29.0 + '@svgr/babel-plugin-add-jsx-attribute': 8.0.0(@babel/core@7.29.0) + '@svgr/babel-plugin-remove-jsx-attribute': 8.0.0(@babel/core@7.29.0) + '@svgr/babel-plugin-remove-jsx-empty-expression': 8.0.0(@babel/core@7.29.0) + '@svgr/babel-plugin-replace-jsx-attribute-value': 8.0.0(@babel/core@7.29.0) + '@svgr/babel-plugin-svg-dynamic-title': 8.0.0(@babel/core@7.29.0) + '@svgr/babel-plugin-svg-em-dimensions': 8.0.0(@babel/core@7.29.0) + '@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.29.0) + '@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.29.0) '@svgr/core@8.1.0(typescript@6.0.3)': dependencies: - '@babel/core': 7.28.5 - '@svgr/babel-preset': 8.1.0(@babel/core@7.28.5) + '@babel/core': 7.29.0 + '@svgr/babel-preset': 8.1.0(@babel/core@7.29.0) camelcase: 6.3.0 cosmiconfig: 8.3.6(typescript@6.0.3) snake-case: 3.0.4 @@ -17039,8 +17569,8 @@ snapshots: '@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0(typescript@6.0.3))': dependencies: - '@babel/core': 7.28.5 - '@svgr/babel-preset': 8.1.0(@babel/core@7.28.5) + '@babel/core': 7.29.0 + '@svgr/babel-preset': 8.1.0(@babel/core@7.29.0) '@svgr/core': 8.1.0(typescript@6.0.3) '@svgr/hast-util-to-babel-ast': 8.0.0 svg-parser: 2.0.4 @@ -17052,17 +17582,17 @@ snapshots: '@svgr/core': 8.1.0(typescript@6.0.3) cosmiconfig: 8.3.6(typescript@6.0.3) deepmerge: 4.3.1 - svgo: 3.3.2 + svgo: 3.3.3 transitivePeerDependencies: - typescript '@svgr/webpack@8.1.0(typescript@6.0.3)': dependencies: - '@babel/core': 7.28.5 - '@babel/plugin-transform-react-constant-elements': 7.27.1(@babel/core@7.28.5) - '@babel/preset-env': 7.28.5(@babel/core@7.28.5) - '@babel/preset-react': 7.28.5(@babel/core@7.28.5) - '@babel/preset-typescript': 7.28.5(@babel/core@7.28.5) + '@babel/core': 7.29.0 + '@babel/plugin-transform-react-constant-elements': 7.27.1(@babel/core@7.29.0) + '@babel/preset-env': 7.29.5(@babel/core@7.29.0) + '@babel/preset-react': 7.28.5(@babel/core@7.29.0) + '@babel/preset-typescript': 7.28.5(@babel/core@7.29.0) '@svgr/core': 8.1.0(typescript@6.0.3) '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@6.0.3)) '@svgr/plugin-svgo': 8.1.0(@svgr/core@8.1.0(typescript@6.0.3))(typescript@6.0.3) @@ -17070,64 +17600,64 @@ snapshots: - supports-color - typescript - '@swc/core-darwin-arm64@1.15.30': + '@swc/core-darwin-arm64@1.15.33': optional: true - '@swc/core-darwin-x64@1.15.30': + '@swc/core-darwin-x64@1.15.33': optional: true - '@swc/core-linux-arm-gnueabihf@1.15.30': + '@swc/core-linux-arm-gnueabihf@1.15.33': optional: true - '@swc/core-linux-arm64-gnu@1.15.30': + '@swc/core-linux-arm64-gnu@1.15.33': optional: true - '@swc/core-linux-arm64-musl@1.15.30': + '@swc/core-linux-arm64-musl@1.15.33': optional: true - '@swc/core-linux-ppc64-gnu@1.15.30': + '@swc/core-linux-ppc64-gnu@1.15.33': optional: true - '@swc/core-linux-s390x-gnu@1.15.30': + '@swc/core-linux-s390x-gnu@1.15.33': optional: true - '@swc/core-linux-x64-gnu@1.15.30': + '@swc/core-linux-x64-gnu@1.15.33': optional: true - '@swc/core-linux-x64-musl@1.15.30': + '@swc/core-linux-x64-musl@1.15.33': optional: true - '@swc/core-win32-arm64-msvc@1.15.30': + '@swc/core-win32-arm64-msvc@1.15.33': optional: true - '@swc/core-win32-ia32-msvc@1.15.30': + '@swc/core-win32-ia32-msvc@1.15.33': optional: true - '@swc/core-win32-x64-msvc@1.15.30': + '@swc/core-win32-x64-msvc@1.15.33': optional: true - '@swc/core@1.15.30(@swc/helpers@0.5.21)': + '@swc/core@1.15.33(@swc/helpers@0.5.23)': dependencies: '@swc/counter': 0.1.3 '@swc/types': 0.1.26 optionalDependencies: - '@swc/core-darwin-arm64': 1.15.30 - '@swc/core-darwin-x64': 1.15.30 - '@swc/core-linux-arm-gnueabihf': 1.15.30 - '@swc/core-linux-arm64-gnu': 1.15.30 - '@swc/core-linux-arm64-musl': 1.15.30 - '@swc/core-linux-ppc64-gnu': 1.15.30 - '@swc/core-linux-s390x-gnu': 1.15.30 - '@swc/core-linux-x64-gnu': 1.15.30 - '@swc/core-linux-x64-musl': 1.15.30 - '@swc/core-win32-arm64-msvc': 1.15.30 - '@swc/core-win32-ia32-msvc': 1.15.30 - '@swc/core-win32-x64-msvc': 1.15.30 - '@swc/helpers': 0.5.21 + '@swc/core-darwin-arm64': 1.15.33 + '@swc/core-darwin-x64': 1.15.33 + '@swc/core-linux-arm-gnueabihf': 1.15.33 + '@swc/core-linux-arm64-gnu': 1.15.33 + '@swc/core-linux-arm64-musl': 1.15.33 + '@swc/core-linux-ppc64-gnu': 1.15.33 + '@swc/core-linux-s390x-gnu': 1.15.33 + '@swc/core-linux-x64-gnu': 1.15.33 + '@swc/core-linux-x64-musl': 1.15.33 + '@swc/core-win32-arm64-msvc': 1.15.33 + '@swc/core-win32-ia32-msvc': 1.15.33 + '@swc/core-win32-x64-msvc': 1.15.33 + '@swc/helpers': 0.5.23 '@swc/counter@0.1.3': {} - '@swc/helpers@0.5.21': + '@swc/helpers@0.5.23': dependencies: tslib: 2.8.1 @@ -17139,78 +17669,78 @@ snapshots: dependencies: defer-to-connect: 2.0.1 - '@tailwindcss/node@4.2.4': + '@tailwindcss/node@4.3.0': dependencies: '@jridgewell/remapping': 2.3.5 - enhanced-resolve: 5.21.0 - jiti: 2.6.1 + enhanced-resolve: 5.21.5 + jiti: 2.7.0 lightningcss: 1.32.0 magic-string: 0.30.21 source-map-js: 1.2.1 - tailwindcss: 4.2.4 + tailwindcss: 4.3.0 - '@tailwindcss/oxide-android-arm64@4.2.4': + '@tailwindcss/oxide-android-arm64@4.3.0': optional: true - '@tailwindcss/oxide-darwin-arm64@4.2.4': + '@tailwindcss/oxide-darwin-arm64@4.3.0': optional: true - '@tailwindcss/oxide-darwin-x64@4.2.4': + '@tailwindcss/oxide-darwin-x64@4.3.0': optional: true - '@tailwindcss/oxide-freebsd-x64@4.2.4': + '@tailwindcss/oxide-freebsd-x64@4.3.0': optional: true - '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.4': + '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.0': optional: true - '@tailwindcss/oxide-linux-arm64-gnu@4.2.4': + '@tailwindcss/oxide-linux-arm64-gnu@4.3.0': optional: true - '@tailwindcss/oxide-linux-arm64-musl@4.2.4': + '@tailwindcss/oxide-linux-arm64-musl@4.3.0': optional: true - '@tailwindcss/oxide-linux-x64-gnu@4.2.4': + '@tailwindcss/oxide-linux-x64-gnu@4.3.0': optional: true - '@tailwindcss/oxide-linux-x64-musl@4.2.4': + '@tailwindcss/oxide-linux-x64-musl@4.3.0': optional: true - '@tailwindcss/oxide-wasm32-wasi@4.2.4': + '@tailwindcss/oxide-wasm32-wasi@4.3.0': optional: true - '@tailwindcss/oxide-win32-arm64-msvc@4.2.4': + '@tailwindcss/oxide-win32-arm64-msvc@4.3.0': optional: true - '@tailwindcss/oxide-win32-x64-msvc@4.2.4': + '@tailwindcss/oxide-win32-x64-msvc@4.3.0': optional: true - '@tailwindcss/oxide@4.2.4': + '@tailwindcss/oxide@4.3.0': optionalDependencies: - '@tailwindcss/oxide-android-arm64': 4.2.4 - '@tailwindcss/oxide-darwin-arm64': 4.2.4 - '@tailwindcss/oxide-darwin-x64': 4.2.4 - '@tailwindcss/oxide-freebsd-x64': 4.2.4 - '@tailwindcss/oxide-linux-arm-gnueabihf': 4.2.4 - '@tailwindcss/oxide-linux-arm64-gnu': 4.2.4 - '@tailwindcss/oxide-linux-arm64-musl': 4.2.4 - '@tailwindcss/oxide-linux-x64-gnu': 4.2.4 - '@tailwindcss/oxide-linux-x64-musl': 4.2.4 - '@tailwindcss/oxide-wasm32-wasi': 4.2.4 - '@tailwindcss/oxide-win32-arm64-msvc': 4.2.4 - '@tailwindcss/oxide-win32-x64-msvc': 4.2.4 + '@tailwindcss/oxide-android-arm64': 4.3.0 + '@tailwindcss/oxide-darwin-arm64': 4.3.0 + '@tailwindcss/oxide-darwin-x64': 4.3.0 + '@tailwindcss/oxide-freebsd-x64': 4.3.0 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.3.0 + '@tailwindcss/oxide-linux-arm64-gnu': 4.3.0 + '@tailwindcss/oxide-linux-arm64-musl': 4.3.0 + '@tailwindcss/oxide-linux-x64-gnu': 4.3.0 + '@tailwindcss/oxide-linux-x64-musl': 4.3.0 + '@tailwindcss/oxide-wasm32-wasi': 4.3.0 + '@tailwindcss/oxide-win32-arm64-msvc': 4.3.0 + '@tailwindcss/oxide-win32-x64-msvc': 4.3.0 - '@tailwindcss/vite@4.2.4(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': + '@tailwindcss/vite@4.3.0(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0))': dependencies: - '@tailwindcss/node': 4.2.4 - '@tailwindcss/oxide': 4.2.4 - tailwindcss: 4.2.4 - vite: 8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + '@tailwindcss/node': 4.3.0 + '@tailwindcss/oxide': 4.3.0 + tailwindcss: 4.3.0 + vite: 8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0) '@testing-library/dom@10.4.1': dependencies: '@babel/code-frame': 7.29.0 - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 '@types/aria-query': 5.0.4 aria-query: 5.3.0 dom-accessibility-api: 0.5.16 @@ -17227,18 +17757,18 @@ snapshots: picocolors: 1.1.1 redent: 3.0.0 - '@testing-library/svelte-core@1.0.0(svelte@5.55.2)': + '@testing-library/svelte-core@1.0.0(svelte@5.55.8(@typescript-eslint/types@8.59.4))': dependencies: - svelte: 5.55.2 + svelte: 5.55.8(@typescript-eslint/types@8.59.4) - '@testing-library/svelte@5.3.1(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.5)': + '@testing-library/svelte@5.3.1(svelte@5.55.8(@typescript-eslint/types@8.59.4))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0))(vitest@4.1.7)': dependencies: '@testing-library/dom': 10.4.1 - '@testing-library/svelte-core': 1.0.0(svelte@5.55.2) - svelte: 5.55.2 + '@testing-library/svelte-core': 1.0.0(svelte@5.55.8(@typescript-eslint/types@8.59.4)) + svelte: 5.55.8(@typescript-eslint/types@8.59.4) optionalDependencies: - vite: 8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) - vitest: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(@vitest/coverage-v8@4.1.5)(happy-dom@20.9.0)(jsdom@26.1.0(canvas@2.11.2))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + vite: 8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0) + vitest: 4.1.7(@opentelemetry/api@1.9.1)(@types/node@24.12.4)(@vitest/coverage-v8@4.1.7)(happy-dom@20.9.0)(jsdom@26.1.0(canvas@3.2.3))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)) '@testing-library/user-event@14.6.1(@testing-library/dom@10.4.1)': dependencies: @@ -17253,10 +17783,10 @@ snapshots: '@tokenizer/token@0.3.0': {} - '@trivago/prettier-plugin-sort-imports@6.0.2(prettier-plugin-svelte@3.5.1(prettier@3.8.3)(svelte@5.55.2))(prettier@3.8.3)(svelte@5.55.2)': + '@trivago/prettier-plugin-sort-imports@6.0.2(prettier-plugin-svelte@4.1.0(prettier@3.8.3)(svelte@5.55.8(@typescript-eslint/types@8.59.4)))(prettier@3.8.3)(svelte@5.55.8(@typescript-eslint/types@8.59.4))': dependencies: '@babel/generator': 7.29.1 - '@babel/parser': 7.29.2 + '@babel/parser': 7.29.3 '@babel/traverse': 7.29.0 '@babel/types': 7.29.0 javascript-natural-sort: 0.7.1 @@ -17265,40 +17795,38 @@ snapshots: parse-imports-exports: 0.2.4 prettier: 3.8.3 optionalDependencies: - prettier-plugin-svelte: 3.5.1(prettier@3.8.3)(svelte@5.55.2) - svelte: 5.55.2 + prettier-plugin-svelte: 4.1.0(prettier@3.8.3)(svelte@5.55.8(@typescript-eslint/types@8.59.4)) + svelte: 5.55.8(@typescript-eslint/types@8.59.4) transitivePeerDependencies: - supports-color - '@trysound/sax@0.2.0': {} - - '@turf/boolean-point-in-polygon@7.3.4': + '@turf/boolean-point-in-polygon@7.3.5': dependencies: - '@turf/helpers': 7.3.4 - '@turf/invariant': 7.3.4 + '@turf/helpers': 7.3.5 + '@turf/invariant': 7.3.5 '@types/geojson': 7946.0.16 point-in-polygon-hao: 1.2.4 tslib: 2.8.1 - '@turf/helpers@7.3.4': + '@turf/helpers@7.3.5': dependencies: '@types/geojson': 7946.0.16 tslib: 2.8.1 - '@turf/invariant@7.3.4': + '@turf/invariant@7.3.5': dependencies: - '@turf/helpers': 7.3.4 + '@turf/helpers': 7.3.5 '@types/geojson': 7946.0.16 tslib: 2.8.1 - '@tybys/wasm-util@0.10.1': + '@tybys/wasm-util@0.10.2': dependencies: tslib: 2.8.1 optional: true '@types/accepts@1.3.7': dependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@types/archiver@7.0.0': dependencies: @@ -17310,16 +17838,16 @@ snapshots: '@types/bcrypt@6.0.0': dependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@types/body-parser@1.19.6': dependencies: '@types/connect': 3.4.38 - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@types/bonjour@3.5.13': dependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@types/braces@3.0.5': {} @@ -17330,32 +17858,32 @@ snapshots: '@types/deep-eql': 4.0.2 assertion-error: 2.0.1 - '@types/chrome@0.1.32': + '@types/chrome@0.1.42': dependencies: '@types/filesystem': 0.0.36 '@types/har-format': 1.2.16 '@types/chromecast-caf-sender@1.0.11': dependencies: - '@types/chrome': 0.1.32 + '@types/chrome': 0.1.42 '@types/cli-progress@3.11.6': dependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@types/compression@1.8.1': dependencies: '@types/express': 5.0.6 - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@types/connect-history-api-fallback@1.5.4': dependencies: - '@types/express-serve-static-core': 5.1.0 - '@types/node': 24.12.2 + '@types/express-serve-static-core': 5.1.1 + '@types/node': 24.12.4 '@types/connect@3.4.38': dependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@types/content-disposition@0.5.9': {} @@ -17372,11 +17900,11 @@ snapshots: '@types/connect': 3.4.38 '@types/express': 5.0.6 '@types/keygrip': 1.0.6 - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@types/cors@2.8.19': dependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@types/d3-array@3.2.2': {} @@ -17495,7 +18023,7 @@ snapshots: '@types/d3-transition': 3.0.9 '@types/d3-zoom': 3.0.8 - '@types/debug@4.1.12': + '@types/debug@4.1.13': dependencies: '@types/ms': 2.1.0 @@ -17503,13 +18031,13 @@ snapshots: '@types/docker-modem@3.0.6': dependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@types/ssh2': 1.15.5 '@types/dockerode@4.0.1': dependencies: '@types/docker-modem': 3.0.6 - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@types/ssh2': 1.15.5 '@types/dom-to-image@2.6.7': {} @@ -17517,46 +18045,48 @@ snapshots: '@types/eslint-scope@3.7.7': dependencies: '@types/eslint': 9.6.1 - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 '@types/eslint@9.6.1': dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 '@types/json-schema': 7.0.15 '@types/esrecurse@4.3.1': {} '@types/estree-jsx@1.0.5': dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 '@types/estree@1.0.8': {} - '@types/express-serve-static-core@4.19.7': + '@types/estree@1.0.9': {} + + '@types/express-serve-static-core@4.19.8': dependencies: - '@types/node': 24.12.2 - '@types/qs': 6.14.0 + '@types/node': 24.12.4 + '@types/qs': 6.15.1 '@types/range-parser': 1.2.7 '@types/send': 1.2.1 - '@types/express-serve-static-core@5.1.0': + '@types/express-serve-static-core@5.1.1': dependencies: - '@types/node': 24.12.2 - '@types/qs': 6.14.0 + '@types/node': 24.12.4 + '@types/qs': 6.15.1 '@types/range-parser': 1.2.7 '@types/send': 1.2.1 '@types/express@4.17.25': dependencies: '@types/body-parser': 1.19.6 - '@types/express-serve-static-core': 4.19.7 - '@types/qs': 6.14.0 + '@types/express-serve-static-core': 4.19.8 + '@types/qs': 6.15.1 '@types/serve-static': 1.15.10 '@types/express@5.0.6': dependencies: '@types/body-parser': 1.19.6 - '@types/express-serve-static-core': 5.1.0 + '@types/express-serve-static-core': 5.1.1 '@types/serve-static': 2.2.0 '@types/filesystem@0.0.36': @@ -17567,7 +18097,7 @@ snapshots: '@types/fluent-ffmpeg@2.1.28': dependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@types/geojson@7946.0.16': {} @@ -17589,13 +18119,13 @@ snapshots: '@types/http-assert@1.5.6': {} - '@types/http-cache-semantics@4.0.4': {} + '@types/http-cache-semantics@4.2.0': {} '@types/http-errors@2.0.5': {} '@types/http-proxy@1.17.17': dependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@types/inquirer@8.2.12': dependencies: @@ -17619,7 +18149,7 @@ snapshots: '@types/jsonwebtoken@9.0.10': dependencies: '@types/ms': 2.1.0 - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@types/justified-layout@4.1.4': {} @@ -17627,9 +18157,9 @@ snapshots: '@types/koa-compose@3.2.9': dependencies: - '@types/koa': 3.0.1 + '@types/koa': 3.0.3 - '@types/koa@3.0.1': + '@types/koa@3.0.3': dependencies: '@types/accepts': 1.3.7 '@types/content-disposition': 0.5.9 @@ -17638,7 +18168,7 @@ snapshots: '@types/http-errors': 2.0.5 '@types/keygrip': 1.0.6 '@types/koa-compose': 3.2.9 - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@types/leaflet@1.9.21': dependencies: @@ -17668,7 +18198,7 @@ snapshots: '@types/mock-fs@4.13.4': dependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@types/ms@2.1.0': {} @@ -17676,34 +18206,25 @@ snapshots: dependencies: '@types/express': 5.0.6 - '@types/node-forge@1.3.14': - dependencies: - '@types/node': 24.12.2 - '@types/node@17.0.45': {} '@types/node@18.19.130': dependencies: undici-types: 5.26.5 - '@types/node@24.12.2': + '@types/node@24.12.4': dependencies: undici-types: 7.16.0 - '@types/node@25.6.0': - dependencies: - undici-types: 7.19.2 - optional: true - '@types/nodemailer@8.0.0': dependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@types/oidc-provider@9.5.0': dependencies: '@types/keygrip': 1.0.6 - '@types/koa': 3.0.1 - '@types/node': 24.12.2 + '@types/koa': 3.0.3 + '@types/node': 24.12.4 '@types/parse5@5.0.3': {} @@ -17713,73 +18234,73 @@ snapshots: '@types/pg@8.15.6': dependencies: - '@types/node': 24.12.2 - pg-protocol: 1.13.0 + '@types/node': 24.12.4 + pg-protocol: 1.14.0 pg-types: 2.2.0 '@types/pg@8.20.0': dependencies: - '@types/node': 24.12.2 - pg-protocol: 1.13.0 + '@types/node': 24.12.4 + pg-protocol: 1.14.0 pg-types: 2.2.0 '@types/picomatch@4.0.3': {} '@types/pngjs@6.0.5': dependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 - '@types/prismjs@1.26.5': {} + '@types/prismjs@1.26.6': {} '@types/qrcode@1.5.6': dependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 - '@types/qs@6.14.0': {} + '@types/qs@6.15.1': {} '@types/range-parser@1.2.7': {} '@types/react-router-config@5.0.11': dependencies: '@types/history': 4.7.11 - '@types/react': 19.2.14 + '@types/react': 19.2.15 '@types/react-router': 5.1.20 '@types/react-router-dom@5.3.3': dependencies: '@types/history': 4.7.11 - '@types/react': 19.2.14 + '@types/react': 19.2.15 '@types/react-router': 5.1.20 '@types/react-router@5.1.20': dependencies: '@types/history': 4.7.11 - '@types/react': 19.2.14 + '@types/react': 19.2.15 - '@types/react@19.2.14': + '@types/react@19.2.15': dependencies: csstype: 3.2.3 '@types/readdir-glob@1.1.5': dependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@types/retry@0.12.2': {} '@types/sax@1.2.7': dependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@types/semver@7.7.1': {} '@types/send@0.17.6': dependencies: '@types/mime': 1.3.5 - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@types/send@1.2.1': dependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@types/serve-index@1.9.4': dependencies: @@ -17788,36 +18309,36 @@ snapshots: '@types/serve-static@1.15.10': dependencies: '@types/http-errors': 2.0.5 - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@types/send': 0.17.6 '@types/serve-static@2.2.0': dependencies: '@types/http-errors': 2.0.5 - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@types/sockjs@0.3.36': dependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@types/ssh2-streams@0.1.13': dependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@types/ssh2@0.5.52': dependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@types/ssh2-streams': 0.1.13 '@types/ssh2@1.15.5': dependencies: '@types/node': 18.19.130 - '@types/superagent@8.1.9': + '@types/superagent@8.1.10': dependencies: '@types/cookiejar': 2.1.5 '@types/methods': 1.1.4 - '@types/node': 24.12.2 + '@types/node': 24.12.4 form-data: 4.0.5 '@types/supercluster@7.1.3': @@ -17827,11 +18348,11 @@ snapshots: '@types/supertest@7.2.0': dependencies: '@types/methods': 1.1.4 - '@types/superagent': 8.1.9 + '@types/superagent': 8.1.10 '@types/through@0.0.33': dependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@types/trusted-types@2.0.7': {} @@ -17847,7 +18368,7 @@ snapshots: '@types/ws@8.18.1': dependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@types/yargs-parser@21.0.3': {} @@ -17855,15 +18376,15 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 - '@typescript-eslint/eslint-plugin@8.59.0(@typescript-eslint/parser@8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3))(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3)': + '@typescript-eslint/eslint-plugin@8.59.4(@typescript-eslint/parser@8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) - '@typescript-eslint/scope-manager': 8.59.0 - '@typescript-eslint/type-utils': 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) - '@typescript-eslint/utils': 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.59.0 - eslint: 10.2.1(jiti@2.6.1) + '@typescript-eslint/parser': 8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.59.4 + '@typescript-eslint/type-utils': 8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/utils': 8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.59.4 + eslint: 10.4.0(jiti@2.7.0) ignore: 7.0.5 natural-compare: 1.4.0 ts-api-utils: 2.5.0(typescript@6.0.3) @@ -17871,121 +18392,119 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3)': + '@typescript-eslint/parser@8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: - '@typescript-eslint/scope-manager': 8.59.0 - '@typescript-eslint/types': 8.59.0 - '@typescript-eslint/typescript-estree': 8.59.0(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.59.0 + '@typescript-eslint/scope-manager': 8.59.4 + '@typescript-eslint/types': 8.59.4 + '@typescript-eslint/typescript-estree': 8.59.4(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.59.4 debug: 4.4.3 - eslint: 10.2.1(jiti@2.6.1) + eslint: 10.4.0(jiti@2.7.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.59.0(typescript@6.0.3)': + '@typescript-eslint/project-service@8.59.4(typescript@6.0.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.59.0(typescript@6.0.3) - '@typescript-eslint/types': 8.59.0 + '@typescript-eslint/tsconfig-utils': 8.59.4(typescript@6.0.3) + '@typescript-eslint/types': 8.59.4 debug: 4.4.3 typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.59.0': + '@typescript-eslint/scope-manager@8.59.4': dependencies: - '@typescript-eslint/types': 8.59.0 - '@typescript-eslint/visitor-keys': 8.59.0 + '@typescript-eslint/types': 8.59.4 + '@typescript-eslint/visitor-keys': 8.59.4 - '@typescript-eslint/tsconfig-utils@8.59.0(typescript@6.0.3)': + '@typescript-eslint/tsconfig-utils@8.59.4(typescript@6.0.3)': dependencies: typescript: 6.0.3 - '@typescript-eslint/type-utils@8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3)': + '@typescript-eslint/type-utils@8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: - '@typescript-eslint/types': 8.59.0 - '@typescript-eslint/typescript-estree': 8.59.0(typescript@6.0.3) - '@typescript-eslint/utils': 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + '@typescript-eslint/types': 8.59.4 + '@typescript-eslint/typescript-estree': 8.59.4(typescript@6.0.3) + '@typescript-eslint/utils': 8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) debug: 4.4.3 - eslint: 10.2.1(jiti@2.6.1) + eslint: 10.4.0(jiti@2.7.0) ts-api-utils: 2.5.0(typescript@6.0.3) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.59.0': {} + '@typescript-eslint/types@8.59.4': {} - '@typescript-eslint/typescript-estree@8.59.0(typescript@6.0.3)': + '@typescript-eslint/typescript-estree@8.59.4(typescript@6.0.3)': dependencies: - '@typescript-eslint/project-service': 8.59.0(typescript@6.0.3) - '@typescript-eslint/tsconfig-utils': 8.59.0(typescript@6.0.3) - '@typescript-eslint/types': 8.59.0 - '@typescript-eslint/visitor-keys': 8.59.0 + '@typescript-eslint/project-service': 8.59.4(typescript@6.0.3) + '@typescript-eslint/tsconfig-utils': 8.59.4(typescript@6.0.3) + '@typescript-eslint/types': 8.59.4 + '@typescript-eslint/visitor-keys': 8.59.4 debug: 4.4.3 minimatch: 10.2.5 - semver: 7.7.4 + semver: 7.8.1 tinyglobby: 0.2.16 ts-api-utils: 2.5.0(typescript@6.0.3) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3)': + '@typescript-eslint/utils@8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.1(jiti@2.6.1)) - '@typescript-eslint/scope-manager': 8.59.0 - '@typescript-eslint/types': 8.59.0 - '@typescript-eslint/typescript-estree': 8.59.0(typescript@6.0.3) - eslint: 10.2.1(jiti@2.6.1) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.0(jiti@2.7.0)) + '@typescript-eslint/scope-manager': 8.59.4 + '@typescript-eslint/types': 8.59.4 + '@typescript-eslint/typescript-estree': 8.59.4(typescript@6.0.3) + eslint: 10.4.0(jiti@2.7.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.59.0': + '@typescript-eslint/visitor-keys@8.59.4': dependencies: - '@typescript-eslint/types': 8.59.0 + '@typescript-eslint/types': 8.59.4 eslint-visitor-keys: 5.0.1 - '@ungap/structured-clone@1.3.0': {} + '@ungap/structured-clone@1.3.1': {} - '@valibot/to-json-schema@1.6.0(valibot@1.3.1(typescript@6.0.3))': + '@upsetjs/venn.js@2.0.0': + optionalDependencies: + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + + '@valibot/to-json-schema@1.7.0(valibot@1.4.0(typescript@6.0.3))': dependencies: - valibot: 1.3.1(typescript@6.0.3) + valibot: 1.4.0(typescript@6.0.3) - '@vercel/oidc@3.0.5': {} - - '@vitest/coverage-v8@3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.12.2)(happy-dom@20.9.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.32.0)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': - dependencies: - '@ampproject/remapping': 2.3.0 - '@bcoe/v8-coverage': 1.0.2 - ast-v8-to-istanbul: 0.3.12 - debug: 4.4.3 - istanbul-lib-coverage: 3.2.2 - istanbul-lib-report: 3.0.1 - istanbul-lib-source-maps: 5.0.6 - istanbul-reports: 3.2.0 - magic-string: 0.30.21 - magicast: 0.3.5 - std-env: 3.10.0 - test-exclude: 7.0.2 - tinyrainbow: 2.0.0 - vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.12.2)(happy-dom@20.9.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.32.0)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) - transitivePeerDependencies: - - supports-color - - '@vitest/coverage-v8@4.1.5(vitest@4.1.5)': + '@vitest/coverage-v8@4.1.7(vitest@3.2.4(@types/debug@4.1.13)(@types/node@24.12.4)(happy-dom@20.9.0)(jiti@2.7.0)(jsdom@26.1.0(canvas@3.2.3))(lightningcss@1.32.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0))': dependencies: '@bcoe/v8-coverage': 1.0.2 - '@vitest/utils': 4.1.5 + '@vitest/utils': 4.1.7 ast-v8-to-istanbul: 1.0.0 istanbul-lib-coverage: 3.2.2 istanbul-lib-report: 3.0.1 istanbul-reports: 3.2.0 - magicast: 0.5.2 + magicast: 0.5.3 obug: 2.1.1 std-env: 4.1.0 tinyrainbow: 3.1.0 - vitest: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(@vitest/coverage-v8@4.1.5)(happy-dom@20.9.0)(jsdom@26.1.0(canvas@2.11.2))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + vitest: 3.2.4(@types/debug@4.1.13)(@types/node@24.12.4)(happy-dom@20.9.0)(jiti@2.7.0)(jsdom@26.1.0(canvas@3.2.3))(lightningcss@1.32.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0) + + '@vitest/coverage-v8@4.1.7(vitest@4.1.7)': + dependencies: + '@bcoe/v8-coverage': 1.0.2 + '@vitest/utils': 4.1.7 + ast-v8-to-istanbul: 1.0.0 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-reports: 3.2.0 + magicast: 0.5.3 + obug: 2.1.1 + std-env: 4.1.0 + tinyrainbow: 3.1.0 + vitest: 4.1.7(@opentelemetry/api@1.9.1)(@types/node@24.12.4)(@vitest/coverage-v8@4.1.7)(happy-dom@20.9.0)(jsdom@26.1.0(canvas@3.2.3))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)) '@vitest/expect@3.2.4': dependencies: @@ -17995,44 +18514,36 @@ snapshots: chai: 5.3.3 tinyrainbow: 2.0.0 - '@vitest/expect@4.1.5': + '@vitest/expect@4.1.7': dependencies: '@standard-schema/spec': 1.1.0 '@types/chai': 5.2.3 - '@vitest/spy': 4.1.5 - '@vitest/utils': 4.1.5 + '@vitest/spy': 4.1.7 + '@vitest/utils': 4.1.7 chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@3.2.4(vite@7.3.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': + '@vitest/mocker@3.2.4(vite@7.3.3(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0))': dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.3.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vite: 7.3.3(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0) - '@vitest/mocker@4.1.5(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': + '@vitest/mocker@4.1.7(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0))': dependencies: - '@vitest/spy': 4.1.5 + '@vitest/spy': 4.1.7 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) - - '@vitest/mocker@4.1.5(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': - dependencies: - '@vitest/spy': 4.1.5 - estree-walker: 3.0.3 - magic-string: 0.30.21 - optionalDependencies: - vite: 8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0) '@vitest/pretty-format@3.2.4': dependencies: tinyrainbow: 2.0.0 - '@vitest/pretty-format@4.1.5': + '@vitest/pretty-format@4.1.7': dependencies: tinyrainbow: 3.1.0 @@ -18042,9 +18553,9 @@ snapshots: pathe: 2.0.3 strip-literal: 3.1.0 - '@vitest/runner@4.1.5': + '@vitest/runner@4.1.7': dependencies: - '@vitest/utils': 4.1.5 + '@vitest/utils': 4.1.7 pathe: 2.0.3 '@vitest/snapshot@3.2.4': @@ -18053,10 +18564,10 @@ snapshots: magic-string: 0.30.21 pathe: 2.0.3 - '@vitest/snapshot@4.1.5': + '@vitest/snapshot@4.1.7': dependencies: - '@vitest/pretty-format': 4.1.5 - '@vitest/utils': 4.1.5 + '@vitest/pretty-format': 4.1.7 + '@vitest/utils': 4.1.7 magic-string: 0.30.21 pathe: 2.0.3 @@ -18064,7 +18575,7 @@ snapshots: dependencies: tinyspy: 4.0.4 - '@vitest/spy@4.1.5': {} + '@vitest/spy@4.1.7': {} '@vitest/utils@3.2.4': dependencies: @@ -18072,9 +18583,9 @@ snapshots: loupe: 3.2.1 tinyrainbow: 2.0.0 - '@vitest/utils@4.1.5': + '@vitest/utils@4.1.7': dependencies: - '@vitest/pretty-format': 4.1.5 + '@vitest/pretty-format': 4.1.7 convert-source-map: 2.0.0 tinyrainbow: 3.1.0 @@ -18162,10 +18673,10 @@ snapshots: dependencies: '@namnode/store': 0.1.0 - '@zoom-image/svelte@0.3.9(svelte@5.55.2)': + '@zoom-image/svelte@0.3.9(svelte@5.55.8(@typescript-eslint/types@8.59.4))': dependencies: '@zoom-image/core': 0.42.0 - svelte: 5.55.2 + svelte: 5.55.8(@typescript-eslint/types@8.59.4) abbrev@1.1.1: {} @@ -18197,7 +18708,7 @@ snapshots: dependencies: acorn: 8.16.0 - acorn-walk@8.3.4: + acorn-walk@8.3.5: dependencies: acorn: 8.16.0 @@ -18211,39 +18722,36 @@ snapshots: transitivePeerDependencies: - supports-color - agent-base@7.1.4: {} + agent-base@7.1.4: + optional: true aggregate-error@3.1.0: dependencies: clean-stack: 2.2.0 indent-string: 4.0.0 - ai@5.0.113(zod@4.3.6): - dependencies: - '@ai-sdk/gateway': 2.0.21(zod@4.3.6) - '@ai-sdk/provider': 2.0.0 - '@ai-sdk/provider-utils': 3.0.19(zod@4.3.6) - '@opentelemetry/api': 1.9.0 - zod: 4.3.6 - - ajv-formats@2.1.1(ajv@8.18.0): + ajv-formats@2.1.1(ajv@8.20.0): optionalDependencies: - ajv: 8.18.0 + ajv: 8.20.0 ajv-formats@3.0.1(ajv@8.18.0): optionalDependencies: ajv: 8.18.0 - ajv-keywords@3.5.2(ajv@6.14.0): - dependencies: - ajv: 6.14.0 + ajv-formats@3.0.1(ajv@8.20.0): + optionalDependencies: + ajv: 8.20.0 - ajv-keywords@5.1.0(ajv@8.18.0): + ajv-keywords@3.5.2(ajv@6.15.0): dependencies: - ajv: 8.18.0 + ajv: 6.15.0 + + ajv-keywords@5.1.0(ajv@8.20.0): + dependencies: + ajv: 8.20.0 fast-deep-equal: 3.1.3 - ajv@6.14.0: + ajv@6.15.0: dependencies: fast-deep-equal: 3.1.3 fast-json-stable-stringify: 2.1.0 @@ -18253,31 +18761,38 @@ snapshots: ajv@8.18.0: dependencies: fast-deep-equal: 3.1.3 - fast-uri: 3.1.0 + fast-uri: 3.1.2 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 - algoliasearch-helper@3.26.1(algoliasearch@5.46.0): + ajv@8.20.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.2 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + algoliasearch-helper@3.29.1(algoliasearch@5.52.1): dependencies: '@algolia/events': 4.0.1 - algoliasearch: 5.46.0 + algoliasearch: 5.52.1 - algoliasearch@5.46.0: + algoliasearch@5.52.1: dependencies: - '@algolia/abtesting': 1.12.0 - '@algolia/client-abtesting': 5.46.0 - '@algolia/client-analytics': 5.46.0 - '@algolia/client-common': 5.46.0 - '@algolia/client-insights': 5.46.0 - '@algolia/client-personalization': 5.46.0 - '@algolia/client-query-suggestions': 5.46.0 - '@algolia/client-search': 5.46.0 - '@algolia/ingestion': 1.46.0 - '@algolia/monitoring': 1.46.0 - '@algolia/recommend': 5.46.0 - '@algolia/requester-browser-xhr': 5.46.0 - '@algolia/requester-fetch': 5.46.0 - '@algolia/requester-node-http': 5.46.0 + '@algolia/abtesting': 1.18.1 + '@algolia/client-abtesting': 5.52.1 + '@algolia/client-analytics': 5.52.1 + '@algolia/client-common': 5.52.1 + '@algolia/client-insights': 5.52.1 + '@algolia/client-personalization': 5.52.1 + '@algolia/client-query-suggestions': 5.52.1 + '@algolia/client-search': 5.52.1 + '@algolia/ingestion': 1.52.1 + '@algolia/monitoring': 1.52.1 + '@algolia/recommend': 5.52.1 + '@algolia/requester-browser-xhr': 5.52.1 + '@algolia/requester-fetch': 5.52.1 + '@algolia/requester-node-http': 5.52.1 ansi-align@3.0.1: dependencies: @@ -18335,7 +18850,7 @@ snapshots: buffer-crc32: 1.0.0 readable-stream: 4.7.0 readdir-glob: 1.1.3 - tar-stream: 3.1.8 + tar-stream: 3.2.0 zip-stream: 6.0.1 transitivePeerDependencies: - bare-abort-controller @@ -18377,18 +18892,18 @@ snapshots: dependencies: safer-buffer: 2.1.2 + asn1js@3.0.10: + dependencies: + pvtsutils: 1.3.6 + pvutils: 1.1.5 + tslib: 2.8.1 + assertion-error@2.0.1: {} ast-metadata-inferer@0.8.1: dependencies: '@mdn/browser-compat-data': 5.7.6 - ast-v8-to-istanbul@0.3.12: - dependencies: - '@jridgewell/trace-mapping': 0.3.31 - estree-walker: 3.0.3 - js-tokens: 10.0.0 - ast-v8-to-istanbul@1.0.0: dependencies: '@jridgewell/trace-mapping': 0.3.31 @@ -18414,51 +18929,59 @@ snapshots: dependencies: immediate: 3.3.0 - autoprefixer@10.5.0(postcss@8.5.12): + autoprefixer@10.5.0(postcss@8.5.15): dependencies: browserslist: 4.28.2 - caniuse-lite: 1.0.30001790 + caniuse-lite: 1.0.30001793 fraction.js: 5.3.4 picocolors: 1.1.1 - postcss: 8.5.12 + postcss: 8.5.15 postcss-value-parser: 4.2.0 axobject-query@4.1.0: {} - b4a@1.8.0: {} + b4a@1.8.1: {} - babel-loader@9.2.1(@babel/core@7.28.5)(webpack@5.106.2): + babel-loader@9.2.1(@babel/core@7.29.0)(webpack@5.107.0(postcss@8.5.15)): dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 find-cache-dir: 4.0.0 schema-utils: 4.3.3 - webpack: 5.106.2 + webpack: 5.107.0(postcss@8.5.15) babel-plugin-dynamic-import-node@2.3.3: dependencies: object.assign: 4.1.7 - babel-plugin-polyfill-corejs2@0.4.14(@babel/core@7.28.5): + babel-plugin-polyfill-corejs2@0.4.17(@babel/core@7.29.0): dependencies: - '@babel/compat-data': 7.28.5 - '@babel/core': 7.28.5 - '@babel/helper-define-polyfill-provider': 0.6.5(@babel/core@7.28.5) + '@babel/compat-data': 7.29.3 + '@babel/core': 7.29.0 + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.0) semver: 6.3.1 transitivePeerDependencies: - supports-color - babel-plugin-polyfill-corejs3@0.13.0(@babel/core@7.28.5): + babel-plugin-polyfill-corejs3@0.13.0(@babel/core@7.29.0): dependencies: - '@babel/core': 7.28.5 - '@babel/helper-define-polyfill-provider': 0.6.5(@babel/core@7.28.5) + '@babel/core': 7.29.0 + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.0) core-js-compat: 3.49.0 transitivePeerDependencies: - supports-color - babel-plugin-polyfill-regenerator@0.6.5(@babel/core@7.28.5): + babel-plugin-polyfill-corejs3@0.14.2(@babel/core@7.29.0): dependencies: - '@babel/core': 7.28.5 - '@babel/helper-define-polyfill-provider': 0.6.5(@babel/core@7.28.5) + '@babel/core': 7.29.0 + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.0) + core-js-compat: 3.49.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-polyfill-regenerator@0.6.8(@babel/core@7.29.0): + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.0) transitivePeerDependencies: - supports-color @@ -18470,35 +18993,35 @@ snapshots: balanced-match@4.0.4: {} - bare-events@2.8.2: {} + bare-events@2.8.3: {} bare-fs@4.7.1: dependencies: - bare-events: 2.8.2 + bare-events: 2.8.3 bare-path: 3.0.0 - bare-stream: 2.13.0(bare-events@2.8.2) - bare-url: 2.4.2 + bare-stream: 2.13.1(bare-events@2.8.3) + bare-url: 2.4.3 fast-fifo: 1.3.2 transitivePeerDependencies: - bare-abort-controller - react-native-b4a - bare-os@3.9.0: {} + bare-os@3.9.1: {} bare-path@3.0.0: dependencies: - bare-os: 3.9.0 + bare-os: 3.9.1 - bare-stream@2.13.0(bare-events@2.8.2): + bare-stream@2.13.1(bare-events@2.8.3): dependencies: streamx: 2.25.0 teex: 1.0.1 optionalDependencies: - bare-events: 2.8.2 + bare-events: 2.8.3 transitivePeerDependencies: - react-native-b4a - bare-url@2.4.2: + bare-url@2.4.3: dependencies: bare-path: 3.0.0 @@ -18506,7 +19029,7 @@ snapshots: base64id@2.0.0: {} - baseline-browser-mapping@2.10.20: {} + baseline-browser-mapping@2.10.31: {} batch-cluster@17.3.1: {} @@ -18520,25 +19043,23 @@ snapshots: bcrypt@6.0.0: dependencies: - node-addon-api: 8.5.0 - node-gyp: 12.2.0 + node-addon-api: 8.7.0 + node-gyp: 12.3.0 node-gyp-build: 4.8.4 - transitivePeerDependencies: - - supports-color big.js@5.2.2: {} binary-extensions@2.3.0: {} - bits-ui@2.18.0(@internationalized/date@3.12.1)(@sveltejs/kit@2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2): + bits-ui@2.18.1(@internationalized/date@3.12.2)(@sveltejs/kit@2.60.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.55.8(@typescript-eslint/types@8.59.4))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4))(typescript@6.0.3)(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4)): dependencies: '@floating-ui/core': 1.7.5 '@floating-ui/dom': 1.7.6 - '@internationalized/date': 3.12.1 + '@internationalized/date': 3.12.2 esm-env: 1.2.2 - runed: 0.35.1(@sveltejs/kit@2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2) - svelte: 5.55.2 - svelte-toolbelt: 0.10.6(@sveltejs/kit@2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2) + runed: 0.35.1(@sveltejs/kit@2.60.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.55.8(@typescript-eslint/types@8.59.4))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4))(typescript@6.0.3)(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4)) + svelte: 5.55.8(@typescript-eslint/types@8.59.4) + svelte-toolbelt: 0.10.6(@sveltejs/kit@2.60.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.55.8(@typescript-eslint/types@8.59.4))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4))(typescript@6.0.3)(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4)) tabbable: 6.4.0 transitivePeerDependencies: - '@sveltejs/kit' @@ -18549,7 +19070,7 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 - body-parser@1.20.4: + body-parser@1.20.5: dependencies: bytes: 3.1.2 content-type: 1.0.5 @@ -18559,7 +19080,7 @@ snapshots: http-errors: 2.0.1 iconv-lite: 0.4.24 on-finished: 2.4.1 - qs: 6.14.1 + qs: 6.15.2 raw-body: 2.5.3 type-is: 1.6.18 unpipe: 1.0.0 @@ -18574,9 +19095,9 @@ snapshots: http-errors: 2.0.1 iconv-lite: 0.7.2 on-finished: 2.4.1 - qs: 6.14.1 + qs: 6.15.2 raw-body: 3.0.2 - type-is: 2.0.1 + type-is: 2.1.0 transitivePeerDependencies: - supports-color @@ -18609,7 +19130,7 @@ snapshots: widest-line: 4.0.1 wrap-ansi: 8.1.0 - brace-expansion@1.1.12: + brace-expansion@1.1.14: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 @@ -18618,7 +19139,7 @@ snapshots: dependencies: balanced-match: 1.0.2 - brace-expansion@5.0.5: + brace-expansion@5.0.6: dependencies: balanced-match: 4.0.4 @@ -18628,10 +19149,10 @@ snapshots: browserslist@4.28.2: dependencies: - baseline-browser-mapping: 2.10.20 - caniuse-lite: 1.0.30001790 - electron-to-chromium: 1.5.343 - node-releases: 2.0.38 + baseline-browser-mapping: 2.10.31 + caniuse-lite: 1.0.30001793 + electron-to-chromium: 1.5.360 + node-releases: 2.0.44 update-browserslist-db: 1.2.3(browserslist@4.28.2) buffer-crc32@1.0.0: {} @@ -18653,17 +19174,16 @@ snapshots: buildcheck@0.0.7: optional: true - builtin-modules@5.0.0: {} + builtin-modules@5.2.0: {} - bullmq@5.76.1: + bullmq@5.76.10: dependencies: cron-parser: 4.9.0 ioredis: 5.10.1 - msgpackr: 1.11.5 + msgpackr: 2.0.1 node-abort-controller: 3.1.1 - semver: 7.7.4 + semver: 7.8.0 tslib: 2.8.1 - uuid: 11.1.0 transitivePeerDependencies: - supports-color @@ -18683,32 +19203,20 @@ snapshots: bytes@3.1.2: {} - cac@6.7.14: {} + bytestreamjs@2.0.1: {} - cacache@20.0.3: - dependencies: - '@npmcli/fs': 5.0.0 - fs-minipass: 3.0.3 - glob: 13.0.6 - lru-cache: 11.3.5 - minipass: 7.1.3 - minipass-collect: 2.0.1 - minipass-flush: 1.0.5 - minipass-pipeline: 1.2.4 - p-map: 7.0.4 - ssri: 13.0.1 - unique-filename: 5.0.0 + cac@6.7.14: {} cacheable-lookup@7.0.0: {} cacheable-request@10.2.14: dependencies: - '@types/http-cache-semantics': 4.0.4 + '@types/http-cache-semantics': 4.2.0 get-stream: 6.0.1 http-cache-semantics: 4.2.0 keyv: 4.5.4 mimic-response: 4.0.0 - normalize-url: 8.1.0 + normalize-url: 8.1.1 responselike: 3.0.0 call-bind-apply-helpers@1.0.2: @@ -18716,7 +19224,7 @@ snapshots: es-errors: 1.3.0 function-bind: 1.1.2 - call-bind@1.0.8: + call-bind@1.0.9: dependencies: call-bind-apply-helpers: 1.0.2 es-define-property: 1.0.1 @@ -18746,37 +19254,23 @@ snapshots: caniuse-api@3.0.0: dependencies: browserslist: 4.28.2 - caniuse-lite: 1.0.30001790 + caniuse-lite: 1.0.30001793 lodash.memoize: 4.1.2 lodash.uniq: 4.5.0 - caniuse-lite@1.0.30001790: {} + caniuse-lite@1.0.30001793: {} - canvas@2.11.2: + canvas@3.2.3: dependencies: - '@mapbox/node-pre-gyp': 1.0.11 - nan: 2.26.2 - simple-get: 3.1.1 - transitivePeerDependencies: - - encoding - - supports-color - optional: true - - canvas@2.11.2(encoding@0.1.13): - dependencies: - '@mapbox/node-pre-gyp': 1.0.11(encoding@0.1.13) - nan: 2.26.2 - simple-get: 3.1.1 - transitivePeerDependencies: - - encoding - - supports-color + node-addon-api: 7.1.1 + prebuild-install: 7.1.3 optional: true ccount@2.0.1: {} - ce-la-react@0.3.2(react@19.2.5): + ce-la-react@0.3.2(react@19.2.6): dependencies: - react: 19.2.5 + react: 19.2.6 chai@5.3.3: dependencies: @@ -18830,20 +19324,6 @@ snapshots: parse5: 7.3.0 parse5-htmlparser2-tree-adapter: 7.1.0 - chevrotain-allstar@0.3.1(chevrotain@11.0.3): - dependencies: - chevrotain: 11.0.3 - lodash-es: 4.18.1 - - chevrotain@11.0.3: - dependencies: - '@chevrotain/cst-dts-gen': 11.0.3 - '@chevrotain/gast': 11.0.3 - '@chevrotain/regexp-to-ast': 11.0.3 - '@chevrotain/types': 11.0.3 - '@chevrotain/utils': 11.0.3 - lodash-es: 4.17.21 - chokidar@3.6.0: dependencies: anymatch: 3.1.3 @@ -18876,9 +19356,6 @@ snapshots: cjs-module-lexer@2.2.0: {} - class-transformer@0.5.1: - optional: true - clean-css@5.3.3: dependencies: source-map: 0.6.1 @@ -18953,11 +19430,11 @@ snapshots: cluster-key-slot@1.1.2: {} - codemirror-wrapped-line-indent@1.0.9(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.41.1): + codemirror-wrapped-line-indent@1.0.9(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.43.0): dependencies: '@codemirror/language': 6.12.3 '@codemirror/state': 6.6.0 - '@codemirror/view': 6.41.1 + '@codemirror/view': 6.43.0 collapse-white-space@2.1.0: {} @@ -19003,6 +19480,8 @@ snapshots: commander@8.3.0: {} + commander@9.5.0: {} + comment-json@5.0.0: dependencies: array-timsort: 1.0.3 @@ -19047,18 +19526,16 @@ snapshots: conf@15.1.0: dependencies: - ajv: 8.18.0 - ajv-formats: 3.0.1(ajv@8.18.0) + ajv: 8.20.0 + ajv-formats: 3.0.1(ajv@8.20.0) atomically: 2.1.1 debounce-fn: 6.0.0 dot-prop: 10.1.0 env-paths: 3.0.0 json-schema-typed: 8.0.2 - semver: 7.7.4 + semver: 7.8.1 uint8array-extras: 1.5.0 - confbox@0.1.8: {} - config-chain@1.1.13: dependencies: ini: 1.3.8 @@ -19086,8 +19563,12 @@ snapshots: content-disposition@1.0.1: {} + content-disposition@1.1.0: {} + content-type@1.0.5: {} + content-type@2.0.0: {} + convert-source-map@2.0.0: {} cookie-parser@1.4.7: @@ -19116,7 +19597,7 @@ snapshots: copy-text-to-clipboard@3.2.2: {} - copy-webpack-plugin@11.0.0(webpack@5.106.2): + copy-webpack-plugin@11.0.0(webpack@5.107.0(postcss@8.5.15)): dependencies: fast-glob: 3.3.3 glob-parent: 6.0.2 @@ -19124,13 +19605,13 @@ snapshots: normalize-path: 3.0.0 schema-utils: 4.3.3 serialize-javascript: 6.0.2 - webpack: 5.106.2 + webpack: 5.107.0(postcss@8.5.15) core-js-compat@3.49.0: dependencies: browserslist: 4.28.2 - core-js@3.47.0: {} + core-js@3.49.0: {} core-util-is@1.0.3: {} @@ -19168,7 +19649,7 @@ snapshots: cpu-features@0.0.10: dependencies: buildcheck: 0.0.7 - nan: 2.26.2 + nan: 2.27.0 optional: true crc-32@1.2.2: {} @@ -19199,50 +19680,50 @@ snapshots: dependencies: type-fest: 1.4.0 - css-blank-pseudo@7.0.1(postcss@8.5.12): + css-blank-pseudo@7.0.1(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-selector-parser: 7.1.1 - css-declaration-sorter@7.3.0(postcss@8.5.12): + css-declaration-sorter@7.4.0(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 - css-has-pseudo@7.0.3(postcss@8.5.12): + css-has-pseudo@7.0.3(postcss@8.5.15): dependencies: '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.1) - postcss: 8.5.12 + postcss: 8.5.15 postcss-selector-parser: 7.1.1 postcss-value-parser: 4.2.0 - css-loader@6.11.0(webpack@5.106.2): + css-loader@6.11.0(webpack@5.107.0(postcss@8.5.15)): dependencies: - icss-utils: 5.1.0(postcss@8.5.12) - postcss: 8.5.12 - postcss-modules-extract-imports: 3.1.0(postcss@8.5.12) - postcss-modules-local-by-default: 4.2.0(postcss@8.5.12) - postcss-modules-scope: 3.2.1(postcss@8.5.12) - postcss-modules-values: 4.0.0(postcss@8.5.12) + icss-utils: 5.1.0(postcss@8.5.15) + postcss: 8.5.15 + postcss-modules-extract-imports: 3.1.0(postcss@8.5.15) + postcss-modules-local-by-default: 4.2.0(postcss@8.5.15) + postcss-modules-scope: 3.2.1(postcss@8.5.15) + postcss-modules-values: 4.0.0(postcss@8.5.15) postcss-value-parser: 4.2.0 - semver: 7.7.4 + semver: 7.8.1 optionalDependencies: - webpack: 5.106.2 + webpack: 5.107.0(postcss@8.5.15) - css-minimizer-webpack-plugin@5.0.1(clean-css@5.3.3)(webpack@5.106.2): + css-minimizer-webpack-plugin@5.0.1(clean-css@5.3.3)(webpack@5.107.0(postcss@8.5.15)): dependencies: '@jridgewell/trace-mapping': 0.3.31 - cssnano: 6.1.2(postcss@8.5.12) + cssnano: 6.1.2(postcss@8.5.15) jest-worker: 29.7.0 - postcss: 8.5.12 + postcss: 8.5.15 schema-utils: 4.3.3 serialize-javascript: 6.0.2 - webpack: 5.106.2 + webpack: 5.107.0(postcss@8.5.15) optionalDependencies: clean-css: 5.3.3 - css-prefers-color-scheme@10.0.0(postcss@8.5.12): + css-prefers-color-scheme@10.0.0(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 css-select@4.3.0: dependencies: @@ -19276,64 +19757,64 @@ snapshots: css.escape@1.5.1: {} - cssdb@8.5.2: {} + cssdb@8.9.0: {} cssesc@3.0.0: {} - cssnano-preset-advanced@6.1.2(postcss@8.5.12): + cssnano-preset-advanced@6.1.2(postcss@8.5.15): dependencies: - autoprefixer: 10.5.0(postcss@8.5.12) + autoprefixer: 10.5.0(postcss@8.5.15) browserslist: 4.28.2 - cssnano-preset-default: 6.1.2(postcss@8.5.12) - postcss: 8.5.12 - postcss-discard-unused: 6.0.5(postcss@8.5.12) - postcss-merge-idents: 6.0.3(postcss@8.5.12) - postcss-reduce-idents: 6.0.3(postcss@8.5.12) - postcss-zindex: 6.0.2(postcss@8.5.12) + cssnano-preset-default: 6.1.2(postcss@8.5.15) + postcss: 8.5.15 + postcss-discard-unused: 6.0.5(postcss@8.5.15) + postcss-merge-idents: 6.0.3(postcss@8.5.15) + postcss-reduce-idents: 6.0.3(postcss@8.5.15) + postcss-zindex: 6.0.2(postcss@8.5.15) - cssnano-preset-default@6.1.2(postcss@8.5.12): + cssnano-preset-default@6.1.2(postcss@8.5.15): dependencies: browserslist: 4.28.2 - css-declaration-sorter: 7.3.0(postcss@8.5.12) - cssnano-utils: 4.0.2(postcss@8.5.12) - postcss: 8.5.12 - postcss-calc: 9.0.1(postcss@8.5.12) - postcss-colormin: 6.1.0(postcss@8.5.12) - postcss-convert-values: 6.1.0(postcss@8.5.12) - postcss-discard-comments: 6.0.2(postcss@8.5.12) - postcss-discard-duplicates: 6.0.3(postcss@8.5.12) - postcss-discard-empty: 6.0.3(postcss@8.5.12) - postcss-discard-overridden: 6.0.2(postcss@8.5.12) - postcss-merge-longhand: 6.0.5(postcss@8.5.12) - postcss-merge-rules: 6.1.1(postcss@8.5.12) - postcss-minify-font-values: 6.1.0(postcss@8.5.12) - postcss-minify-gradients: 6.0.3(postcss@8.5.12) - postcss-minify-params: 6.1.0(postcss@8.5.12) - postcss-minify-selectors: 6.0.4(postcss@8.5.12) - postcss-normalize-charset: 6.0.2(postcss@8.5.12) - postcss-normalize-display-values: 6.0.2(postcss@8.5.12) - postcss-normalize-positions: 6.0.2(postcss@8.5.12) - postcss-normalize-repeat-style: 6.0.2(postcss@8.5.12) - postcss-normalize-string: 6.0.2(postcss@8.5.12) - postcss-normalize-timing-functions: 6.0.2(postcss@8.5.12) - postcss-normalize-unicode: 6.1.0(postcss@8.5.12) - postcss-normalize-url: 6.0.2(postcss@8.5.12) - postcss-normalize-whitespace: 6.0.2(postcss@8.5.12) - postcss-ordered-values: 6.0.2(postcss@8.5.12) - postcss-reduce-initial: 6.1.0(postcss@8.5.12) - postcss-reduce-transforms: 6.0.2(postcss@8.5.12) - postcss-svgo: 6.0.3(postcss@8.5.12) - postcss-unique-selectors: 6.0.4(postcss@8.5.12) + css-declaration-sorter: 7.4.0(postcss@8.5.15) + cssnano-utils: 4.0.2(postcss@8.5.15) + postcss: 8.5.15 + postcss-calc: 9.0.1(postcss@8.5.15) + postcss-colormin: 6.1.0(postcss@8.5.15) + postcss-convert-values: 6.1.0(postcss@8.5.15) + postcss-discard-comments: 6.0.2(postcss@8.5.15) + postcss-discard-duplicates: 6.0.3(postcss@8.5.15) + postcss-discard-empty: 6.0.3(postcss@8.5.15) + postcss-discard-overridden: 6.0.2(postcss@8.5.15) + postcss-merge-longhand: 6.0.5(postcss@8.5.15) + postcss-merge-rules: 6.1.1(postcss@8.5.15) + postcss-minify-font-values: 6.1.0(postcss@8.5.15) + postcss-minify-gradients: 6.0.3(postcss@8.5.15) + postcss-minify-params: 6.1.0(postcss@8.5.15) + postcss-minify-selectors: 6.0.4(postcss@8.5.15) + postcss-normalize-charset: 6.0.2(postcss@8.5.15) + postcss-normalize-display-values: 6.0.2(postcss@8.5.15) + postcss-normalize-positions: 6.0.2(postcss@8.5.15) + postcss-normalize-repeat-style: 6.0.2(postcss@8.5.15) + postcss-normalize-string: 6.0.2(postcss@8.5.15) + postcss-normalize-timing-functions: 6.0.2(postcss@8.5.15) + postcss-normalize-unicode: 6.1.0(postcss@8.5.15) + postcss-normalize-url: 6.0.2(postcss@8.5.15) + postcss-normalize-whitespace: 6.0.2(postcss@8.5.15) + postcss-ordered-values: 6.0.2(postcss@8.5.15) + postcss-reduce-initial: 6.1.0(postcss@8.5.15) + postcss-reduce-transforms: 6.0.2(postcss@8.5.15) + postcss-svgo: 6.0.3(postcss@8.5.15) + postcss-unique-selectors: 6.0.4(postcss@8.5.15) - cssnano-utils@4.0.2(postcss@8.5.12): + cssnano-utils@4.0.2(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 - cssnano@6.1.2(postcss@8.5.12): + cssnano@6.1.2(postcss@8.5.15): dependencies: - cssnano-preset-default: 6.1.2(postcss@8.5.12) + cssnano-preset-default: 6.1.2(postcss@8.5.15) lilconfig: 3.1.3 - postcss: 8.5.12 + postcss: 8.5.15 csso@5.0.5: dependencies: @@ -19347,17 +19828,19 @@ snapshots: csstype@3.2.3: {} - cytoscape-cose-bilkent@4.1.0(cytoscape@3.33.1): + custom-media-element@1.4.6: {} + + cytoscape-cose-bilkent@4.1.0(cytoscape@3.33.4): dependencies: cose-base: 1.0.3 - cytoscape: 3.33.1 + cytoscape: 3.33.4 - cytoscape-fcose@2.2.0(cytoscape@3.33.1): + cytoscape-fcose@2.2.0(cytoscape@3.33.4): dependencies: cose-base: 2.2.0 - cytoscape: 3.33.1 + cytoscape: 3.33.4 - cytoscape@3.33.1: {} + cytoscape@3.33.4: {} d3-array@2.12.1: dependencies: @@ -19389,7 +19872,7 @@ snapshots: d3-delaunay@6.0.4: dependencies: - delaunator: 5.0.1 + delaunator: 5.1.0 d3-dispatch@3.0.1: {} @@ -19416,7 +19899,7 @@ snapshots: d3-quadtree: 3.0.1 d3-timer: 3.0.1 - d3-format@3.1.0: {} + d3-format@3.1.2: {} d3-geo@3.1.1: dependencies: @@ -19451,7 +19934,7 @@ snapshots: d3-scale@4.0.2: dependencies: d3-array: 3.2.4 - d3-format: 3.1.0 + d3-format: 3.1.2 d3-interpolate: 3.0.1 d3-time: 3.1.0 d3-time-format: 4.1.0 @@ -19508,7 +19991,7 @@ snapshots: d3-ease: 3.0.1 d3-fetch: 3.0.1 d3-force: 3.0.0 - d3-format: 3.1.0 + d3-format: 3.1.2 d3-geo: 3.1.1 d3-hierarchy: 3.1.2 d3-interpolate: 3.0.1 @@ -19531,7 +20014,7 @@ snapshots: es5-ext: 0.10.64 type: 2.7.3 - dagre-d3-es@7.0.13: + dagre-d3-es@7.0.14: dependencies: d3: 7.9.0 lodash-es: 4.18.1 @@ -19542,7 +20025,7 @@ snapshots: whatwg-url: 14.2.0 optional: true - dayjs@1.11.19: {} + dayjs@1.11.20: {} debounce-fn@6.0.0: dependencies: @@ -19568,15 +20051,10 @@ snapshots: decimal.js@10.6.0: {} - decode-named-character-reference@1.2.0: + decode-named-character-reference@1.3.0: dependencies: character-entities: 2.0.2 - decompress-response@4.2.1: - dependencies: - mimic-response: 2.1.0 - optional: true - decompress-response@6.0.0: dependencies: mimic-response: 3.1.0 @@ -19593,7 +20071,7 @@ snapshots: default-browser-id@5.0.1: {} - default-browser@5.4.0: + default-browser@5.5.0: dependencies: bundle-name: 4.1.0 default-browser-id: 5.0.1 @@ -19620,7 +20098,7 @@ snapshots: has-property-descriptors: 1.0.2 object-keys: 1.1.1 - delaunator@5.0.1: + delaunator@5.1.0: dependencies: robust-predicates: 3.0.3 @@ -19651,7 +20129,7 @@ snapshots: transitivePeerDependencies: - supports-color - devalue@5.7.1: {} + devalue@5.8.1: {} devlop@1.1.0: dependencies: @@ -19686,7 +20164,7 @@ snapshots: docker-compose@1.4.2: dependencies: - yaml: 2.8.3 + yaml: 2.9.0 docker-modem@5.0.7: dependencies: @@ -19697,21 +20175,20 @@ snapshots: transitivePeerDependencies: - supports-color - dockerode@4.0.10: + dockerode@5.0.0: dependencies: '@balena/dockerignore': 1.0.2 '@grpc/grpc-js': 1.14.3 '@grpc/proto-loader': 0.7.15 docker-modem: 5.0.7 - protobufjs: 7.5.5 + protobufjs: 7.6.0 tar-fs: 2.1.4 - uuid: 10.0.0 transitivePeerDependencies: - supports-color - docusaurus-lunr-search@3.6.0(@docusaurus/core@3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3))(react-dom@19.2.5(react@19.2.5))(react@19.2.5): + docusaurus-lunr-search@3.6.0(@docusaurus/core@3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6): dependencies: - '@docusaurus/core': 3.10.0(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) + '@docusaurus/core': 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.15)(react@19.2.6))(postcss@8.5.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3) autocomplete.js: 0.37.1 clsx: 2.1.1 gauge: 3.0.2 @@ -19719,11 +20196,11 @@ snapshots: hast-util-to-text: 2.0.1 hogan.js: 3.0.2 lunr: 2.3.9 - lunr-languages: 1.14.0 + lunr-languages: 1.20.0 mark.js: 8.11.1 minimatch: 3.1.5 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) rehype-parse: 7.0.1 to-vfile: 6.1.0 unified: 9.2.2 @@ -19761,7 +20238,7 @@ snapshots: dependencies: domelementtype: 2.3.0 - dompurify@3.3.1: + dompurify@3.4.5: optionalDependencies: '@types/trusted-types': 2.0.7 @@ -19810,7 +20287,7 @@ snapshots: ee-first@1.1.1: {} - electron-to-chromium@1.5.343: {} + electron-to-chromium@1.5.360: {} emoji-regex@10.6.0: {} @@ -19826,21 +20303,16 @@ snapshots: encodeurl@2.0.0: {} - encoding@0.1.13: - dependencies: - iconv-lite: 0.6.3 - optional: true - end-of-stream@1.4.5: dependencies: once: 1.4.0 - engine.io-client@6.6.4: + engine.io-client@6.6.5: dependencies: '@socket.io/component-emitter': 3.1.2 debug: 4.4.3 engine.io-parser: 5.2.3 - ws: 8.18.3 + ws: 8.20.1 xmlhttprequest-ssl: 2.1.2 transitivePeerDependencies: - bufferutil @@ -19849,23 +20321,24 @@ snapshots: engine.io-parser@5.2.3: {} - engine.io@6.6.5: + engine.io@6.6.8: dependencies: '@types/cors': 2.8.19 - '@types/node': 24.12.2 + '@types/node': 24.12.4 + '@types/ws': 8.18.1 accepts: 1.3.8 base64id: 2.0.0 cookie: 0.7.2 cors: 2.8.6 debug: 4.4.3 engine.io-parser: 5.2.3 - ws: 8.18.3 + ws: 8.20.1 transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate - enhanced-resolve@5.21.0: + enhanced-resolve@5.21.5: dependencies: graceful-fs: 4.2.11 tapable: 2.3.3 @@ -19882,8 +20355,6 @@ snapshots: env-paths@3.0.0: {} - err-code@2.0.3: {} - error-ex@1.3.4: dependencies: is-arrayish: 0.2.1 @@ -19905,7 +20376,9 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.3.0 has-tostringtag: 1.0.2 - hasown: 2.0.2 + hasown: 2.0.3 + + es-toolkit@1.46.1: {} es5-ext@0.10.64: dependencies: @@ -20001,6 +20474,35 @@ snapshots: '@esbuild/win32-ia32': 0.27.4 '@esbuild/win32-x64': 0.27.4 + esbuild@0.27.7: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.7 + '@esbuild/android-arm': 0.27.7 + '@esbuild/android-arm64': 0.27.7 + '@esbuild/android-x64': 0.27.7 + '@esbuild/darwin-arm64': 0.27.7 + '@esbuild/darwin-x64': 0.27.7 + '@esbuild/freebsd-arm64': 0.27.7 + '@esbuild/freebsd-x64': 0.27.7 + '@esbuild/linux-arm': 0.27.7 + '@esbuild/linux-arm64': 0.27.7 + '@esbuild/linux-ia32': 0.27.7 + '@esbuild/linux-loong64': 0.27.7 + '@esbuild/linux-mips64el': 0.27.7 + '@esbuild/linux-ppc64': 0.27.7 + '@esbuild/linux-riscv64': 0.27.7 + '@esbuild/linux-s390x': 0.27.7 + '@esbuild/linux-x64': 0.27.7 + '@esbuild/netbsd-arm64': 0.27.7 + '@esbuild/netbsd-x64': 0.27.7 + '@esbuild/openbsd-arm64': 0.27.7 + '@esbuild/openbsd-x64': 0.27.7 + '@esbuild/openharmony-arm64': 0.27.7 + '@esbuild/sunos-x64': 0.27.7 + '@esbuild/win32-arm64': 0.27.7 + '@esbuild/win32-ia32': 0.27.7 + '@esbuild/win32-x64': 0.27.7 + esbuild@0.28.0: optionalDependencies: '@esbuild/aix-ppc64': 0.28.0 @@ -20042,84 +20544,84 @@ snapshots: escape-string-regexp@5.0.0: {} - eslint-config-prettier@10.1.8(eslint@10.2.1(jiti@2.6.1)): + eslint-config-prettier@10.1.8(eslint@10.4.0(jiti@2.7.0)): dependencies: - eslint: 10.2.1(jiti@2.6.1) + eslint: 10.4.0(jiti@2.7.0) - eslint-plugin-better-tailwindcss@4.5.0(eslint@10.2.1(jiti@2.6.1))(tailwindcss@4.2.4)(typescript@6.0.3): + eslint-plugin-better-tailwindcss@4.5.0(eslint@10.4.0(jiti@2.7.0))(tailwindcss@4.3.0)(typescript@6.0.3): dependencies: - '@eslint/css-tree': 4.0.2 - '@valibot/to-json-schema': 1.6.0(valibot@1.3.1(typescript@6.0.3)) - enhanced-resolve: 5.21.0 - jiti: 2.6.1 + '@eslint/css-tree': 4.0.3 + '@valibot/to-json-schema': 1.7.0(valibot@1.4.0(typescript@6.0.3)) + enhanced-resolve: 5.21.5 + jiti: 2.7.0 synckit: 0.11.12 tailwind-csstree: 0.3.1 - tailwindcss: 4.2.4 + tailwindcss: 4.3.0 tsconfig-paths-webpack-plugin: 4.2.0 - valibot: 1.3.1(typescript@6.0.3) + valibot: 1.4.0(typescript@6.0.3) optionalDependencies: - eslint: 10.2.1(jiti@2.6.1) + eslint: 10.4.0(jiti@2.7.0) transitivePeerDependencies: - '@eslint/css' - typescript - eslint-plugin-compat@7.0.1(eslint@10.2.1(jiti@2.6.1)): + eslint-plugin-compat@7.0.2(eslint@10.4.0(jiti@2.7.0)): dependencies: '@mdn/browser-compat-data': 6.1.5 ast-metadata-inferer: 0.8.1 browserslist: 4.28.2 - eslint: 10.2.1(jiti@2.6.1) + eslint: 10.4.0(jiti@2.7.0) find-up: 5.0.0 globals: 15.15.0 lodash.memoize: 4.1.2 - semver: 7.7.4 + semver: 7.8.1 - eslint-plugin-prettier@5.5.5(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@10.2.1(jiti@2.6.1)))(eslint@10.2.1(jiti@2.6.1))(prettier@3.8.3): + eslint-plugin-prettier@5.5.5(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@10.4.0(jiti@2.7.0)))(eslint@10.4.0(jiti@2.7.0))(prettier@3.8.3): dependencies: - eslint: 10.2.1(jiti@2.6.1) + eslint: 10.4.0(jiti@2.7.0) prettier: 3.8.3 prettier-linter-helpers: 1.0.1 synckit: 0.11.12 optionalDependencies: '@types/eslint': 9.6.1 - eslint-config-prettier: 10.1.8(eslint@10.2.1(jiti@2.6.1)) + eslint-config-prettier: 10.1.8(eslint@10.4.0(jiti@2.7.0)) - eslint-plugin-svelte@3.17.1(eslint@10.2.1(jiti@2.6.1))(svelte@5.55.2): + eslint-plugin-svelte@3.17.1(eslint@10.4.0(jiti@2.7.0))(svelte@5.55.8(@typescript-eslint/types@8.59.4)): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.1(jiti@2.6.1)) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.0(jiti@2.7.0)) '@jridgewell/sourcemap-codec': 1.5.5 - eslint: 10.2.1(jiti@2.6.1) + eslint: 10.4.0(jiti@2.7.0) esutils: 2.0.3 globals: 16.5.0 known-css-properties: 0.37.0 - postcss: 8.5.12 - postcss-load-config: 3.1.4(postcss@8.5.12) - postcss-safe-parser: 7.0.1(postcss@8.5.12) - semver: 7.7.4 - svelte-eslint-parser: 1.6.0(svelte@5.55.2) + postcss: 8.5.15 + postcss-load-config: 3.1.4(postcss@8.5.15) + postcss-safe-parser: 7.0.1(postcss@8.5.15) + semver: 7.8.1 + svelte-eslint-parser: 1.6.1(svelte@5.55.8(@typescript-eslint/types@8.59.4)) optionalDependencies: - svelte: 5.55.2 + svelte: 5.55.8(@typescript-eslint/types@8.59.4) transitivePeerDependencies: - ts-node - eslint-plugin-unicorn@64.0.0(eslint@10.2.1(jiti@2.6.1)): + eslint-plugin-unicorn@64.0.0(eslint@10.4.0(jiti@2.7.0)): dependencies: '@babel/helper-validator-identifier': 7.28.5 - '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.1(jiti@2.6.1)) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.0(jiti@2.7.0)) change-case: 5.4.4 ci-info: 4.4.0 clean-regexp: 1.0.0 core-js-compat: 3.49.0 - eslint: 10.2.1(jiti@2.6.1) + eslint: 10.4.0(jiti@2.7.0) find-up-simple: 1.0.1 - globals: 17.5.0 + globals: 17.6.0 indent-string: 5.0.0 is-builtin-module: 5.0.0 jsesc: 3.1.0 pluralize: 8.0.0 regexp-tree: 0.1.27 - regjsparser: 0.13.0 - semver: 7.7.4 + regjsparser: 0.13.1 + semver: 7.8.1 strip-indent: 4.1.1 eslint-scope@5.1.1: @@ -20135,7 +20637,7 @@ snapshots: eslint-scope@9.1.2: dependencies: '@types/esrecurse': 4.3.1 - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 esrecurse: 4.3.0 estraverse: 5.3.0 @@ -20145,19 +20647,19 @@ snapshots: eslint-visitor-keys@5.0.1: {} - eslint@10.2.1(jiti@2.6.1): + eslint@10.4.0(jiti@2.7.0): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.1(jiti@2.6.1)) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.0(jiti@2.7.0)) '@eslint-community/regexpp': 4.12.2 '@eslint/config-array': 0.23.5 - '@eslint/config-helpers': 0.5.5 + '@eslint/config-helpers': 0.6.0 '@eslint/core': 1.2.1 '@eslint/plugin-kit': 0.7.1 '@humanfs/node': 0.16.8 '@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/retry': 0.4.3 - '@types/estree': 1.0.8 - ajv: 6.14.0 + '@types/estree': 1.0.9 + ajv: 6.15.0 cross-spawn: 7.0.6 debug: 4.4.3 escape-string-regexp: 4.0.0 @@ -20178,7 +20680,7 @@ snapshots: natural-compare: 1.4.0 optionator: 0.9.4 optionalDependencies: - jiti: 2.6.1 + jiti: 2.7.0 transitivePeerDependencies: - supports-color @@ -20209,10 +20711,11 @@ snapshots: dependencies: estraverse: 5.3.0 - esrap@2.2.4: + esrap@2.2.9(@typescript-eslint/types@8.59.4): dependencies: '@jridgewell/sourcemap-codec': 1.5.5 - '@typescript-eslint/types': 8.59.0 + optionalDependencies: + '@typescript-eslint/types': 8.59.4 esrecurse@4.3.0: dependencies: @@ -20224,7 +20727,7 @@ snapshots: estree-util-attach-comments@3.0.0: dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 estree-util-build-jsx@3.0.1: dependencies: @@ -20237,7 +20740,7 @@ snapshots: estree-util-scope@1.0.0: dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 devlop: 1.1.0 estree-util-to-js@2.0.0: @@ -20248,7 +20751,7 @@ snapshots: estree-util-value-to-estree@3.5.0: dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 estree-util-visit@2.0.0: dependencies: @@ -20259,19 +20762,19 @@ snapshots: estree-walker@3.0.3: dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 esutils@2.0.3: {} eta@2.2.0: {} - eta@4.5.1: {} + eta@4.6.0: {} etag@1.8.1: {} eval@0.1.8: dependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 require-like: 0.1.2 event-emitter@0.3.5: @@ -20285,14 +20788,12 @@ snapshots: events-universal@1.0.1: dependencies: - bare-events: 2.8.2 + bare-events: 2.8.3 transitivePeerDependencies: - bare-abort-controller events@3.3.0: {} - eventsource-parser@3.0.6: {} - execa@5.1.1: dependencies: cross-spawn: 7.0.6 @@ -20305,31 +20806,34 @@ snapshots: signal-exit: 3.0.7 strip-final-newline: 2.0.0 - exiftool-vendored.exe@13.57.0: + exiftool-vendored.exe@13.58.0: optional: true - exiftool-vendored.pl@13.57.0: {} + exiftool-vendored.pl@13.58.0: {} - exiftool-vendored@35.18.0: + exiftool-vendored@35.20.0: dependencies: '@photostructure/tz-lookup': 11.5.0 '@types/luxon': 3.7.1 batch-cluster: 17.3.1 - exiftool-vendored.pl: 13.57.0 + exiftool-vendored.pl: 13.58.0 he: 1.2.0 luxon: 3.7.2 optionalDependencies: - exiftool-vendored.exe: 13.57.0 + exiftool-vendored.exe: 13.58.0 + + expand-template@2.0.3: + optional: true expect-type@1.3.0: {} exponential-backoff@3.1.3: {} - express@4.22.1: + express@4.22.2: dependencies: accepts: 1.3.8 array-flatten: 1.1.1 - body-parser: 1.20.4 + body-parser: 1.20.5 content-disposition: 0.5.4 content-type: 1.0.5 cookie: 0.7.2 @@ -20348,7 +20852,7 @@ snapshots: parseurl: 1.3.3 path-to-regexp: 0.1.13 proxy-addr: 2.0.7 - qs: 6.14.1 + qs: 6.15.2 range-parser: 1.2.1 safe-buffer: 5.2.1 send: 0.19.2 @@ -20365,7 +20869,7 @@ snapshots: dependencies: accepts: 2.0.0 body-parser: 2.2.2 - content-disposition: 1.0.1 + content-disposition: 1.1.0 content-type: 1.0.5 cookie: 0.7.2 cookie-signature: 1.2.2 @@ -20383,13 +20887,13 @@ snapshots: once: 1.4.0 parseurl: 1.3.3 proxy-addr: 2.0.7 - qs: 6.14.1 + qs: 6.15.2 range-parser: 1.2.1 router: 2.2.0 send: 1.2.1 serve-static: 2.2.1 statuses: 2.0.2 - type-is: 2.0.1 + type-is: 2.1.0 vary: 1.1.2 transitivePeerDependencies: - supports-color @@ -20404,13 +20908,12 @@ snapshots: extend@3.0.2: {} - fabric@7.3.1: + fabric@7.4.0: optionalDependencies: - canvas: 2.11.2 - jsdom: 26.1.0(canvas@2.11.2) + canvas: 3.2.3 + jsdom: 26.1.0(canvas@3.2.3) transitivePeerDependencies: - bufferutil - - encoding - supports-color - utf-8-validate @@ -20439,7 +20942,7 @@ snapshots: fast-safe-stringify@2.1.1: {} - fast-uri@3.1.0: {} + fast-uri@3.1.2: {} fastq@1.20.1: dependencies: @@ -20461,7 +20964,7 @@ snapshots: dependencies: xml-js: 1.6.11 - fflate@0.8.2: {} + fflate@0.8.3: {} figures@3.2.0: dependencies: @@ -20471,11 +20974,11 @@ snapshots: dependencies: flat-cache: 4.0.1 - file-loader@6.2.0(webpack@5.106.2): + file-loader@6.2.0(webpack@5.107.0(postcss@8.5.15)): dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 - webpack: 5.106.2 + webpack: 5.107.0(postcss@8.5.15) file-source@0.6.1: dependencies: @@ -20553,14 +21056,14 @@ snapshots: async: 0.2.10 which: 1.3.1 - follow-redirects@1.15.11: {} + follow-redirects@1.16.0: {} foreground-child@3.3.1: dependencies: cross-spawn: 7.0.6 signal-exit: 4.1.0 - fork-ts-checker-webpack-plugin@9.1.0(typescript@5.9.3)(webpack@5.106.0(@swc/core@1.15.30(@swc/helpers@0.5.21))(esbuild@0.28.0)): + fork-ts-checker-webpack-plugin@9.1.0(typescript@5.9.3)(webpack@5.106.0(@swc/core@1.15.33(@swc/helpers@0.5.23))(esbuild@0.28.0)(lightningcss@1.32.0)): dependencies: '@babel/code-frame': 7.29.0 chalk: 4.1.2 @@ -20572,10 +21075,10 @@ snapshots: minimatch: 3.1.5 node-abort-controller: 3.1.1 schema-utils: 3.3.0 - semver: 7.7.4 + semver: 7.8.1 tapable: 2.3.3 typescript: 5.9.3 - webpack: 5.106.0(@swc/core@1.15.30(@swc/helpers@0.5.21))(esbuild@0.28.0) + webpack: 5.106.0(@swc/core@1.15.33(@swc/helpers@0.5.23))(esbuild@0.28.0)(lightningcss@1.32.0) form-data-encoder@2.1.4: {} @@ -20584,7 +21087,7 @@ snapshots: asynckit: 0.4.0 combined-stream: 1.0.8 es-set-tostringtag: 2.1.0 - hasown: 2.0.2 + hasown: 2.0.3 mime-types: 2.1.35 format@0.2.2: {} @@ -20605,10 +21108,6 @@ snapshots: fresh@2.0.0: {} - front-matter@4.0.2: - dependencies: - js-yaml: 3.14.2 - fs-constants@1.0.0: {} fs-extra@10.1.0: @@ -20617,7 +21116,7 @@ snapshots: jsonfile: 6.2.1 universalify: 2.0.1 - fs-extra@11.3.4: + fs-extra@11.3.5: dependencies: graceful-fs: 4.2.11 jsonfile: 6.2.1 @@ -20627,10 +21126,6 @@ snapshots: dependencies: minipass: 3.3.6 - fs-minipass@3.0.3: - dependencies: - minipass: 7.1.3 - fs-monkey@1.1.0: {} fs.realpath@1.0.0: {} @@ -20655,14 +21150,16 @@ snapshots: strip-ansi: 6.0.1 wide-align: 1.1.5 + generic-pool@3.9.0: {} + gensync@1.0.0-beta.2: {} geo-coordinates-parser@1.7.4: {} - geo-tz@8.1.6: + geo-tz@8.1.7: dependencies: - '@turf/boolean-point-in-polygon': 7.3.4 - '@turf/helpers': 7.3.4 + '@turf/boolean-point-in-polygon': 7.3.5 + '@turf/helpers': 7.3.5 geobuf: 3.0.2 pbf: 3.3.0 @@ -20676,7 +21173,7 @@ snapshots: get-caller-file@2.0.5: {} - get-east-asian-width@1.5.0: {} + get-east-asian-width@1.6.0: {} get-intrinsic@1.3.0: dependencies: @@ -20688,7 +21185,7 @@ snapshots: get-proto: 1.0.1 gopd: 1.2.0 has-symbols: 1.1.0 - hasown: 2.0.2 + hasown: 2.0.3 math-intrinsics: 1.1.0 get-own-enumerable-property-symbols@3.0.2: {} @@ -20702,10 +21199,13 @@ snapshots: get-stream@6.0.1: {} - get-tsconfig@4.13.0: + get-tsconfig@4.14.0: dependencies: resolve-pkg-maps: 1.0.0 + github-from-package@0.0.0: + optional: true + github-slugger@1.5.0: {} gl-matrix@3.4.4: {} @@ -20758,7 +21258,7 @@ snapshots: globals@16.5.0: {} - globals@17.5.0: {} + globals@17.6.0: {} globalyzer@0.1.0: {} @@ -20829,12 +21329,12 @@ snapshots: happy-dom@20.9.0: dependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 '@types/whatwg-mimetype': 3.0.2 '@types/ws': 8.18.1 entities: 7.0.1 whatwg-mimetype: 3.0.0 - ws: 8.20.0 + ws: 8.20.1 transitivePeerDependencies: - bufferutil - utf-8-validate @@ -20855,7 +21355,7 @@ snapshots: has-yarn@3.0.0: {} - hasown@2.0.2: + hasown@2.0.3: dependencies: function-bind: 1.1.2 @@ -20893,14 +21393,14 @@ snapshots: dependencies: '@types/hast': 3.0.4 '@types/unist': 3.0.3 - '@ungap/structured-clone': 1.3.0 + '@ungap/structured-clone': 1.3.1 hast-util-from-parse5: 8.0.3 hast-util-to-parse5: 8.0.1 html-void-elements: 3.0.0 mdast-util-to-hast: 13.2.1 parse5: 7.3.0 unist-util-position: 5.0.0 - unist-util-visit: 5.0.0 + unist-util-visit: 5.1.0 vfile: 6.0.3 web-namespaces: 2.0.1 zwitch: 2.0.4 @@ -20924,7 +21424,7 @@ snapshots: hast-util-to-estree@3.1.3: dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 '@types/estree-jsx': 1.0.5 '@types/hast': 3.0.4 comma-separated-tokens: 2.0.3 @@ -20945,7 +21445,7 @@ snapshots: hast-util-to-jsx-runtime@2.3.6: dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 '@types/hast': 3.0.4 '@types/unist': 3.0.3 comma-separated-tokens: 2.0.3 @@ -21011,13 +21511,21 @@ snapshots: history@4.10.1: dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 loose-envify: 1.4.0 resolve-pathname: 3.0.0 tiny-invariant: 1.3.3 tiny-warning: 1.0.3 value-equal: 1.0.1 + hls-video-element@1.5.11: + dependencies: + custom-media-element: 1.4.6 + hls.js: 1.6.16 + media-tracks: 0.3.5 + + hls.js@1.6.16: {} + hogan.js@3.0.2: dependencies: mkdirp: 0.3.0 @@ -21049,7 +21557,7 @@ snapshots: he: 1.2.0 param-case: 3.0.4 relateurl: 0.2.7 - terser: 5.46.1 + terser: 5.47.1 html-minifier-terser@7.2.0: dependencies: @@ -21059,7 +21567,7 @@ snapshots: entities: 4.5.0 param-case: 3.0.4 relateurl: 0.2.7 - terser: 5.46.1 + terser: 5.47.1 html-tags@3.3.1: {} @@ -21073,7 +21581,7 @@ snapshots: html-void-elements@3.0.0: {} - html-webpack-plugin@5.6.5(webpack@5.106.2): + html-webpack-plugin@5.6.7(webpack@5.107.0(postcss@8.5.15)): dependencies: '@types/html-minifier-terser': 6.1.0 html-minifier-terser: 6.1.0 @@ -21081,7 +21589,7 @@ snapshots: pretty-error: 4.0.0 tapable: 2.3.3 optionalDependencies: - webpack: 5.106.2 + webpack: 5.107.0(postcss@8.5.15) htmlparser2@6.1.0: dependencies: @@ -21106,13 +21614,6 @@ snapshots: http-deceiver@1.2.7: {} - http-errors@1.6.3: - dependencies: - depd: 1.1.2 - inherits: 2.0.3 - setprototypeof: 1.1.0 - statuses: 1.5.0 - http-errors@1.8.1: dependencies: depd: 1.1.2 @@ -21137,6 +21638,7 @@ snapshots: debug: 4.4.3 transitivePeerDependencies: - supports-color + optional: true http-proxy-middleware@2.0.9(@types/express@4.17.25): dependencies: @@ -21153,7 +21655,7 @@ snapshots: http-proxy@1.18.1: dependencies: eventemitter3: 4.0.7 - follow-redirects: 1.15.11 + follow-redirects: 1.16.0 requires-port: 1.0.0 transitivePeerDependencies: - debug @@ -21176,6 +21678,7 @@ snapshots: debug: 4.4.3 transitivePeerDependencies: - supports-color + optional: true human-signals@2.1.0: {} @@ -21197,9 +21700,9 @@ snapshots: dependencies: safer-buffer: 2.1.2 - icss-utils@5.1.0(postcss@8.5.12): + icss-utils@5.1.0(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 ieee754@1.2.1: {} @@ -21222,7 +21725,7 @@ snapshots: parent-module: 1.0.1 resolve-from: 4.0.0 - import-in-the-middle@3.0.0: + import-in-the-middle@3.0.1: dependencies: acorn: 8.16.0 acorn-import-attributes: 1.9.5(acorn@8.16.0) @@ -21231,6 +21734,8 @@ snapshots: import-lazy@4.0.0: {} + import-meta-resolve@4.2.0: {} + imurmurhash@0.1.4: {} indent-string@4.0.0: {} @@ -21244,8 +21749,6 @@ snapshots: once: 1.4.0 wrappy: 1.0.2 - inherits@2.0.3: {} - inherits@2.0.4: {} ini@1.3.8: {} @@ -21254,9 +21757,9 @@ snapshots: inline-style-parser@0.2.7: {} - inquirer@8.2.7(@types/node@24.12.2): + inquirer@8.2.7(@types/node@24.12.4): dependencies: - '@inquirer/external-editor': 1.0.3(@types/node@24.12.2) + '@inquirer/external-editor': 1.0.3(@types/node@24.12.4) ansi-escapes: 4.3.2 chalk: 4.1.2 cli-cursor: 3.1.0 @@ -21285,10 +21788,10 @@ snapshots: '@formatjs/icu-messageformat-parser': 2.11.4 tslib: 2.8.1 - intl-messageformat@11.2.1: + intl-messageformat@11.2.6: dependencies: - '@formatjs/fast-memoize': 3.1.2 - '@formatjs/icu-messageformat-parser': 3.5.4 + '@formatjs/fast-memoize': 3.1.5 + '@formatjs/icu-messageformat-parser': 3.5.9 invariant@2.2.4: dependencies: @@ -21308,11 +21811,9 @@ snapshots: transitivePeerDependencies: - supports-color - ip-address@10.1.0: {} - ipaddr.js@1.9.1: {} - ipaddr.js@2.3.0: {} + ipaddr.js@2.4.0: {} is-alphabetical@2.0.1: {} @@ -21331,15 +21832,15 @@ snapshots: is-builtin-module@5.0.0: dependencies: - builtin-modules: 5.0.0 + builtin-modules: 5.2.0 is-ci@3.0.1: dependencies: ci-info: 3.9.0 - is-core-module@2.16.1: + is-core-module@2.16.2: dependencies: - hasown: 2.0.2 + hasown: 2.0.3 is-decimal@2.0.1: {} @@ -21374,7 +21875,7 @@ snapshots: is-interactive@2.0.0: {} - is-network-error@1.3.0: {} + is-network-error@1.3.2: {} is-npm@6.1.0: {} @@ -21405,7 +21906,7 @@ snapshots: is-reference@3.0.3: dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 is-regexp@1.0.0: {} @@ -21449,14 +21950,6 @@ snapshots: make-dir: 4.0.0 supports-color: 7.2.0 - istanbul-lib-source-maps@5.0.6: - dependencies: - '@jridgewell/trace-mapping': 0.3.31 - debug: 4.4.3 - istanbul-lib-coverage: 3.2.2 - transitivePeerDependencies: - - supports-color - istanbul-reports@3.2.0: dependencies: html-escaper: 2.0.2 @@ -21475,7 +21968,7 @@ snapshots: jest-util@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 24.12.2 + '@types/node': 24.12.4 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 @@ -21483,13 +21976,13 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 merge-stream: 2.0.0 supports-color: 8.1.1 jest-worker@29.7.0: dependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -21498,7 +21991,7 @@ snapshots: jiti@2.4.2: {} - jiti@2.6.1: {} + jiti@2.7.0: {} jmespath@0.16.0: {} @@ -21510,7 +22003,7 @@ snapshots: '@sideway/formula': 3.0.1 '@sideway/pinpoint': 2.0.0 - jose@6.2.2: {} + jose@6.2.3: {} js-tokens@10.0.0: {} @@ -21527,7 +22020,7 @@ snapshots: dependencies: argparse: 2.0.1 - jsdom@26.1.0(canvas@2.11.2(encoding@0.1.13)): + jsdom@26.1.0(canvas@3.2.3): dependencies: cssstyle: 4.6.0 data-urls: 5.0.0 @@ -21547,40 +22040,10 @@ snapshots: whatwg-encoding: 3.1.1 whatwg-mimetype: 4.0.0 whatwg-url: 14.2.0 - ws: 8.20.0 + ws: 8.20.1 xml-name-validator: 5.0.0 optionalDependencies: - canvas: 2.11.2(encoding@0.1.13) - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - optional: true - - jsdom@26.1.0(canvas@2.11.2): - dependencies: - cssstyle: 4.6.0 - data-urls: 5.0.0 - decimal.js: 10.6.0 - html-encoding-sniffer: 4.0.0 - http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.6 - is-potential-custom-element-name: 1.0.1 - nwsapi: 2.2.23 - parse5: 7.3.0 - rrweb-cssom: 0.8.0 - saxes: 6.0.0 - symbol-tree: 3.2.4 - tough-cookie: 5.1.2 - w3c-xmlserializer: 5.0.0 - webidl-conversions: 7.0.0 - whatwg-encoding: 3.1.1 - whatwg-mimetype: 4.0.0 - whatwg-url: 14.2.0 - ws: 8.20.0 - xml-name-validator: 5.0.0 - optionalDependencies: - canvas: 2.11.2 + canvas: 3.2.3 transitivePeerDependencies: - bufferutil - supports-color @@ -21601,8 +22064,6 @@ snapshots: json-schema-typed@8.0.2: {} - json-schema@0.4.0: {} - json-source-map@0.6.1: {} json-stable-stringify-without-jsonify@1.0.1: {} @@ -21638,7 +22099,7 @@ snapshots: lodash.isstring: 4.0.1 lodash.once: 4.1.1 ms: 2.1.3 - semver: 7.7.4 + semver: 7.8.1 just-compare@2.3.0: {} @@ -21655,11 +22116,11 @@ snapshots: jwa: 2.0.1 safe-buffer: 5.2.1 - katex@0.16.27: + katex@0.16.47: dependencies: commander: 8.3.0 - kdbush@4.0.2: {} + kdbush@4.1.0: {} keygrip@1.1.0: dependencies: @@ -21699,30 +22160,22 @@ snapshots: on-finished: 2.4.1 parseurl: 1.3.3 statuses: 2.0.2 - type-is: 2.0.1 + type-is: 2.1.0 vary: 1.1.2 - kysely-postgres-js@3.0.0(kysely@0.28.16)(postgres@3.4.9): + kysely-postgres-js@3.0.0(kysely@0.28.17)(postgres@3.4.9): dependencies: - kysely: 0.28.16 + kysely: 0.28.17 optionalDependencies: postgres: 3.4.9 - kysely@0.28.16: {} - - langium@3.3.1: - dependencies: - chevrotain: 11.0.3 - chevrotain-allstar: 0.3.1(chevrotain@11.0.3) - vscode-languageserver: 9.0.1 - vscode-languageserver-textdocument: 1.0.12 - vscode-uri: 3.0.8 + kysely@0.28.17: {} latest-version@7.0.0: dependencies: package-json: 8.1.1 - launch-editor@2.12.0: + launch-editor@2.13.2: dependencies: picocolors: 1.1.1 shell-quote: 1.8.3 @@ -21803,7 +22256,7 @@ snapshots: load-tsconfig@0.2.5: {} - loader-runner@4.3.1: {} + loader-runner@4.3.2: {} loader-utils@2.0.4: dependencies: @@ -21825,8 +22278,6 @@ snapshots: dependencies: p-locate: 6.0.0 - lodash-es@4.17.21: {} - lodash-es@4.18.1: {} lodash.camelcase@4.3.0: {} @@ -21890,7 +22341,7 @@ snapshots: lru-cache@10.4.3: {} - lru-cache@11.3.5: {} + lru-cache@11.5.0: {} lru-cache@5.1.1: dependencies: @@ -21900,7 +22351,7 @@ snapshots: dependencies: es5-ext: 0.10.64 - lunr-languages@1.14.0: {} + lunr-languages@1.20.0: {} lunr@2.3.9: {} @@ -21916,15 +22367,9 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 - magicast@0.3.5: + magicast@0.5.3: dependencies: - '@babel/parser': 7.29.2 - '@babel/types': 7.29.0 - source-map-js: 1.2.1 - - magicast@0.5.2: - dependencies: - '@babel/parser': 7.29.2 + '@babel/parser': 7.29.3 '@babel/types': 7.29.0 source-map-js: 1.2.1 @@ -21934,40 +22379,24 @@ snapshots: make-dir@4.0.0: dependencies: - semver: 7.7.4 - - make-fetch-happen@15.0.3: - dependencies: - '@npmcli/agent': 4.0.0 - cacache: 20.0.3 - http-cache-semantics: 4.2.0 - minipass: 7.1.3 - minipass-fetch: 5.0.1 - minipass-flush: 1.0.5 - minipass-pipeline: 1.2.4 - negotiator: 1.0.0 - proc-log: 6.1.0 - promise-retry: 2.0.1 - ssri: 13.0.1 - transitivePeerDependencies: - - supports-color + semver: 7.8.1 maplibre-gl@5.24.0: dependencies: '@mapbox/jsonlint-lines-primitives': 2.0.2 '@mapbox/point-geometry': 1.1.0 - '@mapbox/tiny-sdf': 2.1.0 + '@mapbox/tiny-sdf': 2.2.0 '@mapbox/unitbezier': 0.0.1 '@mapbox/vector-tile': 2.0.4 '@mapbox/whoots-js': 3.1.0 '@maplibre/geojson-vt': 6.1.0 - '@maplibre/maplibre-gl-style-spec': 24.8.1 + '@maplibre/maplibre-gl-style-spec': 24.8.5 '@maplibre/mlt': 1.1.9 '@maplibre/vt-pbf': 4.3.0 '@types/geojson': 7946.0.16 earcut: 3.0.2 gl-matrix: 3.4.4 - kdbush: 4.0.2 + kdbush: 4.1.0 murmurhash-js: 1.0.0 pbf: 4.0.1 potpack: 2.1.0 @@ -21984,8 +22413,6 @@ snapshots: marked@16.4.2: {} - marked@17.0.6: {} - math-intrinsics@1.1.0: {} mdast-util-directive@3.1.0: @@ -21994,7 +22421,7 @@ snapshots: '@types/unist': 3.0.3 ccount: 2.0.1 devlop: 1.1.0 - mdast-util-from-markdown: 2.0.2 + mdast-util-from-markdown: 2.0.3 mdast-util-to-markdown: 2.1.2 parse-entities: 4.0.2 stringify-entities: 4.0.4 @@ -22009,11 +22436,11 @@ snapshots: unist-util-is: 6.0.1 unist-util-visit-parents: 6.0.2 - mdast-util-from-markdown@2.0.2: + mdast-util-from-markdown@2.0.3: dependencies: '@types/mdast': 4.0.4 '@types/unist': 3.0.3 - decode-named-character-reference: 1.2.0 + decode-named-character-reference: 1.3.0 devlop: 1.1.0 mdast-util-to-string: 4.0.0 micromark: 4.0.2 @@ -22031,7 +22458,7 @@ snapshots: '@types/mdast': 4.0.4 devlop: 1.1.0 escape-string-regexp: 5.0.0 - mdast-util-from-markdown: 2.0.2 + mdast-util-from-markdown: 2.0.3 mdast-util-to-markdown: 2.1.2 micromark-extension-frontmatter: 2.0.0 transitivePeerDependencies: @@ -22049,7 +22476,7 @@ snapshots: dependencies: '@types/mdast': 4.0.4 devlop: 1.1.0 - mdast-util-from-markdown: 2.0.2 + mdast-util-from-markdown: 2.0.3 mdast-util-to-markdown: 2.1.2 micromark-util-normalize-identifier: 2.0.1 transitivePeerDependencies: @@ -22058,7 +22485,7 @@ snapshots: mdast-util-gfm-strikethrough@2.0.0: dependencies: '@types/mdast': 4.0.4 - mdast-util-from-markdown: 2.0.2 + mdast-util-from-markdown: 2.0.3 mdast-util-to-markdown: 2.1.2 transitivePeerDependencies: - supports-color @@ -22068,7 +22495,7 @@ snapshots: '@types/mdast': 4.0.4 devlop: 1.1.0 markdown-table: 3.0.4 - mdast-util-from-markdown: 2.0.2 + mdast-util-from-markdown: 2.0.3 mdast-util-to-markdown: 2.1.2 transitivePeerDependencies: - supports-color @@ -22077,14 +22504,14 @@ snapshots: dependencies: '@types/mdast': 4.0.4 devlop: 1.1.0 - mdast-util-from-markdown: 2.0.2 + mdast-util-from-markdown: 2.0.3 mdast-util-to-markdown: 2.1.2 transitivePeerDependencies: - supports-color mdast-util-gfm@3.1.0: dependencies: - mdast-util-from-markdown: 2.0.2 + mdast-util-from-markdown: 2.0.3 mdast-util-gfm-autolink-literal: 2.0.1 mdast-util-gfm-footnote: 2.1.0 mdast-util-gfm-strikethrough: 2.0.0 @@ -22100,7 +22527,7 @@ snapshots: '@types/hast': 3.0.4 '@types/mdast': 4.0.4 devlop: 1.1.0 - mdast-util-from-markdown: 2.0.2 + mdast-util-from-markdown: 2.0.3 mdast-util-to-markdown: 2.1.2 transitivePeerDependencies: - supports-color @@ -22113,7 +22540,7 @@ snapshots: '@types/unist': 3.0.3 ccount: 2.0.1 devlop: 1.1.0 - mdast-util-from-markdown: 2.0.2 + mdast-util-from-markdown: 2.0.3 mdast-util-to-markdown: 2.1.2 parse-entities: 4.0.2 stringify-entities: 4.0.4 @@ -22124,7 +22551,7 @@ snapshots: mdast-util-mdx@3.0.0: dependencies: - mdast-util-from-markdown: 2.0.2 + mdast-util-from-markdown: 2.0.3 mdast-util-mdx-expression: 2.0.1 mdast-util-mdx-jsx: 3.2.0 mdast-util-mdxjs-esm: 2.0.1 @@ -22138,7 +22565,7 @@ snapshots: '@types/hast': 3.0.4 '@types/mdast': 4.0.4 devlop: 1.1.0 - mdast-util-from-markdown: 2.0.2 + mdast-util-from-markdown: 2.0.3 mdast-util-to-markdown: 2.1.2 transitivePeerDependencies: - supports-color @@ -22152,12 +22579,12 @@ snapshots: dependencies: '@types/hast': 3.0.4 '@types/mdast': 4.0.4 - '@ungap/structured-clone': 1.3.0 + '@ungap/structured-clone': 1.3.1 devlop: 1.1.0 micromark-util-sanitize-uri: 2.0.1 trim-lines: 3.0.1 unist-util-position: 5.0.0 - unist-util-visit: 5.0.0 + unist-util-visit: 5.1.0 vfile: 6.0.3 mdast-util-to-markdown@2.1.2: @@ -22169,7 +22596,7 @@ snapshots: mdast-util-to-string: 4.0.0 micromark-util-classify-character: 2.0.1 micromark-util-decode-string: 2.0.1 - unist-util-visit: 5.0.0 + unist-util-visit: 5.1.0 zwitch: 2.0.4 mdast-util-to-string@4.0.0: @@ -22180,14 +22607,16 @@ snapshots: mdn-data@2.0.30: {} - mdn-data@2.27.1: {} + mdn-data@2.28.0: {} - media-chrome@4.19.0(react@19.2.5): + media-chrome@4.19.0(react@19.2.6): dependencies: - ce-la-react: 0.3.2(react@19.2.5) + ce-la-react: 0.3.2(react@19.2.6) transitivePeerDependencies: - react + media-tracks@0.3.5: {} + media-typer@0.3.0: {} media-typer@1.1.0: {} @@ -22209,7 +22638,7 @@ snapshots: '@jsonjoy.com/json-pack': 1.21.0(tslib@2.8.1) '@jsonjoy.com/util': 1.9.0(tslib@2.8.1) glob-to-regex.js: 1.2.0(tslib@2.8.1) - thingies: 2.5.0(tslib@2.8.1) + thingies: 2.6.0(tslib@2.8.1) tree-dump: 1.1.0(tslib@2.8.1) tslib: 2.8.1 @@ -22234,34 +22663,35 @@ snapshots: merge2@1.4.1: {} - mermaid@11.12.2: + mermaid@11.15.0: dependencies: - '@braintree/sanitize-url': 7.1.1 - '@iconify/utils': 3.1.0 - '@mermaid-js/parser': 0.6.3 + '@braintree/sanitize-url': 7.1.2 + '@iconify/utils': 3.1.3 + '@mermaid-js/parser': 1.1.1 '@types/d3': 7.4.3 - cytoscape: 3.33.1 - cytoscape-cose-bilkent: 4.1.0(cytoscape@3.33.1) - cytoscape-fcose: 2.2.0(cytoscape@3.33.1) + '@upsetjs/venn.js': 2.0.0 + cytoscape: 3.33.4 + cytoscape-cose-bilkent: 4.1.0(cytoscape@3.33.4) + cytoscape-fcose: 2.2.0(cytoscape@3.33.4) d3: 7.9.0 d3-sankey: 0.12.3 - dagre-d3-es: 7.0.13 - dayjs: 1.11.19 - dompurify: 3.3.1 - katex: 0.16.27 + dagre-d3-es: 7.0.14 + dayjs: 1.11.20 + dompurify: 3.4.5 + es-toolkit: 1.46.1 + katex: 0.16.47 khroma: 2.1.0 - lodash-es: 4.18.1 marked: 16.4.2 roughjs: 4.6.6 - stylis: 4.3.6 + stylis: 4.4.0 ts-dedent: 2.2.0 - uuid: 11.1.0 + uuid: 14.0.0 methods@1.1.2: {} micromark-core-commonmark@2.0.3: dependencies: - decode-named-character-reference: 1.2.0 + decode-named-character-reference: 1.3.0 devlop: 1.1.0 micromark-factory-destination: 2.0.1 micromark-factory-label: 2.0.1 @@ -22355,7 +22785,7 @@ snapshots: micromark-extension-mdx-expression@3.0.1: dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 devlop: 1.1.0 micromark-factory-mdx-expression: 2.0.3 micromark-factory-space: 2.0.1 @@ -22366,7 +22796,7 @@ snapshots: micromark-extension-mdx-jsx@3.0.2: dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 devlop: 1.1.0 estree-util-is-identifier-name: 3.0.0 micromark-factory-mdx-expression: 2.0.3 @@ -22383,7 +22813,7 @@ snapshots: micromark-extension-mdxjs-esm@3.0.0: dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 devlop: 1.1.0 micromark-core-commonmark: 2.0.3 micromark-util-character: 2.1.1 @@ -22419,7 +22849,7 @@ snapshots: micromark-factory-mdx-expression@2.0.3: dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 devlop: 1.1.0 micromark-factory-space: 2.0.1 micromark-util-character: 2.1.1 @@ -22484,7 +22914,7 @@ snapshots: micromark-util-decode-string@2.0.1: dependencies: - decode-named-character-reference: 1.2.0 + decode-named-character-reference: 1.3.0 micromark-util-character: 2.1.1 micromark-util-decode-numeric-character-reference: 2.0.2 micromark-util-symbol: 2.0.1 @@ -22493,7 +22923,7 @@ snapshots: micromark-util-events-to-acorn@2.0.3: dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 '@types/unist': 3.0.3 devlop: 1.1.0 estree-util-visit: 2.0.0 @@ -22534,9 +22964,9 @@ snapshots: micromark@4.0.2: dependencies: - '@types/debug': 4.1.12 + '@types/debug': 4.1.13 debug: 4.4.3 - decode-named-character-reference: 1.2.0 + decode-named-character-reference: 1.3.0 devlop: 1.1.0 micromark-core-commonmark: 2.0.3 micromark-factory-space: 2.0.1 @@ -22585,30 +23015,27 @@ snapshots: mimic-function@5.0.1: {} - mimic-response@2.1.0: - optional: true - mimic-response@3.1.0: {} mimic-response@4.0.0: {} min-indent@1.0.1: {} - mini-css-extract-plugin@2.9.4(webpack@5.106.2): + mini-css-extract-plugin@2.10.2(webpack@5.107.0(postcss@8.5.15)): dependencies: schema-utils: 4.3.3 tapable: 2.3.3 - webpack: 5.106.2 + webpack: 5.107.0(postcss@8.5.15) minimalistic-assert@1.0.1: {} minimatch@10.2.5: dependencies: - brace-expansion: 5.0.5 + brace-expansion: 5.0.6 minimatch@3.1.5: dependencies: - brace-expansion: 1.1.12 + brace-expansion: 1.1.14 minimatch@5.1.9: dependencies: @@ -22620,30 +23047,6 @@ snapshots: minimist@1.2.8: {} - minipass-collect@2.0.1: - dependencies: - minipass: 7.1.3 - - minipass-fetch@5.0.1: - dependencies: - minipass: 7.1.3 - minipass-sized: 2.0.0 - minizlib: 3.1.0 - optionalDependencies: - encoding: 0.1.13 - - minipass-flush@1.0.5: - dependencies: - minipass: 3.3.6 - - minipass-pipeline@1.2.4: - dependencies: - minipass: 3.3.6 - - minipass-sized@2.0.0: - dependencies: - minipass: 7.1.3 - minipass@3.3.6: dependencies: yallist: 4.0.0 @@ -22669,14 +23072,7 @@ snapshots: mkdirp@3.0.1: {} - mlly@1.8.0: - dependencies: - acorn: 8.16.0 - pathe: 2.0.3 - pkg-types: 1.3.1 - ufo: 1.6.2 - - mnemonist@0.40.3: + mnemonist@0.40.4: dependencies: obliterator: 2.0.5 @@ -22706,7 +23102,7 @@ snapshots: '@msgpackr-extract/msgpackr-extract-win32-x64': 3.0.3 optional: true - msgpackr@1.11.5: + msgpackr@2.0.1: optionalDependencies: msgpackr-extract: 3.0.3 @@ -22728,18 +23124,23 @@ snapshots: mute-stream@2.0.0: {} + mylas@2.1.14: {} + mz@2.7.0: dependencies: any-promise: 1.3.0 object-assign: 4.1.1 thenify-all: 1.6.0 - nan@2.26.2: + nan@2.27.0: optional: true - nanoid@3.3.11: {} + nanoid@3.3.12: {} - nanoid@5.1.9: {} + nanoid@5.1.11: {} + + napi-build-utils@2.0.0: + optional: true natural-compare-lite@1.4.0: {} @@ -22760,52 +23161,51 @@ snapshots: neo-async@2.6.2: {} - nest-commander@3.20.1(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@types/inquirer@8.2.12)(@types/node@24.12.2)(typescript@6.0.3): + nest-commander@3.20.1(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(@types/inquirer@8.2.12)(@types/node@24.12.4)(typescript@6.0.3): dependencies: '@fig/complete-commander': 3.2.0(commander@11.1.0) - '@golevelup/nestjs-discovery': 5.0.0(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19) - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@golevelup/nestjs-discovery': 5.0.0(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21) + '@nestjs/common': 11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.21(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.21)(@nestjs/websockets@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@types/inquirer': 8.2.12 commander: 11.1.0 cosmiconfig: 8.3.6(typescript@6.0.3) - inquirer: 8.2.7(@types/node@24.12.2) + inquirer: 8.2.7(@types/node@24.12.4) transitivePeerDependencies: - '@types/node' - typescript - nestjs-cls@6.2.0(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2): + nestjs-cls@6.2.0(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2): dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.21(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.21)(@nestjs/websockets@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2) reflect-metadata: 0.2.2 rxjs: 7.8.2 - nestjs-kysely@3.1.2(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(kysely@0.28.16)(reflect-metadata@0.2.2): + nestjs-kysely@3.1.2(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(kysely@0.28.17)(reflect-metadata@0.2.2): dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) - kysely: 0.28.16 + '@nestjs/common': 11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.21(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.21)(@nestjs/websockets@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2) + kysely: 0.28.17 reflect-metadata: 0.2.2 tslib: 2.8.1 - nestjs-otel@7.0.1(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19): + nestjs-otel@8.0.3(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21): dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.21(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.21)(@nestjs/websockets@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@opentelemetry/api': 1.9.1 - '@opentelemetry/host-metrics': 0.36.2(@opentelemetry/api@1.9.1) - response-time: 2.3.4 + '@opentelemetry/host-metrics': 0.38.3(@opentelemetry/api@1.9.1) tslib: 2.8.1 - nestjs-zod@5.3.0(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/swagger@11.4.2(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(class-transformer@0.5.1)(reflect-metadata@0.2.2))(rxjs@7.8.2)(zod@4.3.6): + nestjs-zod@5.4.0(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/swagger@11.4.3(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(reflect-metadata@0.2.2)(typescript@6.0.3))(rxjs@7.8.2)(zod@4.3.6): dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2) deepmerge: 4.3.1 rxjs: 7.8.2 zod: 4.3.6 optionalDependencies: - '@nestjs/swagger': 11.4.2(@nestjs/common@11.1.19(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(class-transformer@0.5.1)(reflect-metadata@0.2.2) + '@nestjs/swagger': 11.4.3(@nestjs/common@11.1.21(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(reflect-metadata@0.2.2)(typescript@6.0.3) next-tick@1.1.0: {} @@ -22814,6 +23214,11 @@ snapshots: lower-case: 2.0.2 tslib: 2.8.1 + node-abi@3.92.0: + dependencies: + semver: 7.8.1 + optional: true + node-abort-controller@3.1.1: {} node-addon-api@4.3.0: {} @@ -22821,7 +23226,7 @@ snapshots: node-addon-api@7.1.1: optional: true - node-addon-api@8.5.0: {} + node-addon-api@8.7.0: {} node-emoji@1.11.0: dependencies: @@ -22837,15 +23242,6 @@ snapshots: node-fetch@2.7.0: dependencies: whatwg-url: 5.0.0 - optional: true - - node-fetch@2.7.0(encoding@0.1.13): - dependencies: - whatwg-url: 5.0.0 - optionalDependencies: - encoding: 0.1.13 - - node-forge@1.3.3: {} node-gyp-build-optional-packages@5.2.2: dependencies: @@ -22854,24 +23250,22 @@ snapshots: node-gyp-build@4.8.4: {} - node-gyp@12.2.0: + node-gyp@12.3.0: dependencies: env-paths: 2.2.1 exponential-backoff: 3.1.3 graceful-fs: 4.2.11 - make-fetch-happen: 15.0.3 nopt: 9.0.0 proc-log: 6.1.0 - semver: 7.7.4 - tar: 7.5.7 + semver: 7.8.1 + tar: 7.5.15 tinyglobby: 0.2.16 + undici: 6.25.0 which: 6.0.1 - transitivePeerDependencies: - - supports-color - node-releases@2.0.38: {} + node-releases@2.0.44: {} - nodemailer@8.0.5: {} + nodemailer@8.0.7: {} nopt@1.0.10: dependencies: @@ -22887,7 +23281,7 @@ snapshots: normalize-path@3.0.0: {} - normalize-url@8.1.0: {} + normalize-url@8.1.1: {} not@0.1.0: {} @@ -22910,11 +23304,11 @@ snapshots: dependencies: boolbase: 1.0.0 - null-loader@4.0.1(webpack@5.106.2): + null-loader@4.0.1(webpack@5.107.0(postcss@8.5.15)): dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 - webpack: 5.106.2 + webpack: 5.107.0(postcss@8.5.15) nwsapi@2.2.23: optional: true @@ -22923,9 +23317,9 @@ snapshots: dependencies: citty: 0.2.2 pathe: 2.0.3 - tinyexec: 1.1.1 + tinyexec: 1.1.2 - oauth4webapi@3.8.5: {} + oauth4webapi@3.8.6: {} object-assign@4.1.1: {} @@ -22937,7 +23331,7 @@ snapshots: object.assign@4.1.7: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 es-object-atoms: 1.1.1 @@ -22950,16 +23344,16 @@ snapshots: obug@2.1.1: {} - oidc-provider@9.8.2: + oidc-provider@9.8.3: dependencies: '@koa/cors': 5.0.0 - '@koa/router': 15.4.0(koa@3.2.0) + '@koa/router': 15.5.0(koa@3.2.0) debug: 4.4.3 - eta: 4.5.1 - jose: 6.2.2 + eta: 4.6.0 + jose: 6.2.3 jsesc: 3.1.0 koa: 3.2.0 - nanoid: 5.1.9 + nanoid: 5.1.11 quick-lru: 7.3.0 raw-body: 3.0.2 transitivePeerDependencies: @@ -22985,14 +23379,14 @@ snapshots: open@10.2.0: dependencies: - default-browser: 5.4.0 + default-browser: 5.5.0 define-lazy-prop: 3.0.0 is-inside-container: 1.0.0 wsl-utils: 0.1.0 open@11.0.0: dependencies: - default-browser: 5.4.0 + default-browser: 5.5.0 define-lazy-prop: 3.0.0 is-in-ssh: 1.0.0 is-inside-container: 1.0.0 @@ -23007,10 +23401,10 @@ snapshots: opener@1.5.2: {} - openid-client@6.8.3: + openid-client@6.8.4: dependencies: - jose: 6.2.2 - oauth4webapi: 3.8.5 + jose: 6.2.3 + oauth4webapi: 3.8.6 optionator@0.9.4: dependencies: @@ -23077,8 +23471,6 @@ snapshots: dependencies: aggregate-error: 3.1.0 - p-map@7.0.4: {} - p-queue@6.6.2: dependencies: eventemitter3: 4.0.7 @@ -23087,7 +23479,7 @@ snapshots: p-retry@6.2.1: dependencies: '@types/retry': 0.12.2 - is-network-error: 1.3.0 + is-network-error: 1.3.2 retry: 0.13.1 p-timeout@3.2.0: @@ -23101,9 +23493,9 @@ snapshots: package-json@8.1.1: dependencies: got: 12.6.1 - registry-auth-token: 5.1.0 + registry-auth-token: 5.1.1 registry-url: 6.0.1 - semver: 7.7.4 + semver: 7.8.1 package-manager-detector@1.6.0: {} @@ -23121,7 +23513,7 @@ snapshots: '@types/unist': 2.0.11 character-entities-legacy: 3.0.0 character-reference-invalid: 2.0.1 - decode-named-character-reference: 1.2.0 + decode-named-character-reference: 1.3.0 is-alphanumerical: 2.0.1 is-decimal: 2.0.1 is-hexadecimal: 2.0.1 @@ -23185,7 +23577,7 @@ snapshots: path-scurry@2.0.2: dependencies: - lru-cache: 11.3.5 + lru-cache: 11.5.0 minipass: 7.1.3 path-source@0.1.3: @@ -23220,18 +23612,18 @@ snapshots: peberminta@0.9.0: {} - pg-cloudflare@1.3.0: + pg-cloudflare@1.4.0: optional: true - pg-connection-string@2.12.0: {} + pg-connection-string@2.13.0: {} pg-int8@1.0.1: {} - pg-pool@3.13.0(pg@8.20.0): + pg-pool@3.14.0(pg@8.21.0): dependencies: - pg: 8.20.0 + pg: 8.21.0 - pg-protocol@1.13.0: {} + pg-protocol@1.14.0: {} pg-types@2.2.0: dependencies: @@ -23241,15 +23633,15 @@ snapshots: postgres-date: 1.0.7 postgres-interval: 1.2.0 - pg@8.20.0: + pg@8.21.0: dependencies: - pg-connection-string: 2.12.0 - pg-pool: 3.13.0(pg@8.20.0) - pg-protocol: 1.13.0 + pg-connection-string: 2.13.0 + pg-pool: 3.14.0(pg@8.21.0) + pg-protocol: 1.14.0 pg-types: 2.2.0 pgpass: 1.0.5 optionalDependencies: - pg-cloudflare: 1.3.0 + pg-cloudflare: 1.4.0 pgpass@1.0.5: dependencies: @@ -23269,30 +23661,37 @@ snapshots: dependencies: find-up: 6.3.0 - pkg-types@1.3.1: + pkijs@3.4.0: dependencies: - confbox: 0.1.8 - mlly: 1.8.0 - pathe: 2.0.3 + '@noble/hashes': 1.4.0 + asn1js: 3.0.10 + bytestreamjs: 2.0.1 + pvtsutils: 1.3.6 + pvutils: 1.1.5 + tslib: 2.8.1 - playwright-core@1.59.1: {} + playwright-core@1.60.0: {} - playwright@1.59.1: + playwright@1.60.0: dependencies: - playwright-core: 1.59.1 + playwright-core: 1.60.0 optionalDependencies: fsevents: 2.3.2 + plimit-lit@1.6.1: + dependencies: + queue-lit: 1.5.2 + pluralize@8.0.0: {} pmtiles@3.2.1: dependencies: '@types/leaflet': 1.9.21 - fflate: 0.8.2 + fflate: 0.8.3 pmtiles@4.4.1: dependencies: - fflate: 0.8.2 + fflate: 0.8.3 pngjs@5.0.0: {} @@ -23309,446 +23708,448 @@ snapshots: path-data-parser: 0.1.0 points-on-curve: 0.2.0 - postcss-attribute-case-insensitive@7.0.1(postcss@8.5.12): + postcss-attribute-case-insensitive@7.0.1(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-selector-parser: 7.1.1 - postcss-calc@9.0.1(postcss@8.5.12): + postcss-calc@9.0.1(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-selector-parser: 6.1.2 postcss-value-parser: 4.2.0 - postcss-clamp@4.1.0(postcss@8.5.12): + postcss-clamp@4.1.0(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-color-functional-notation@7.0.12(postcss@8.5.12): + postcss-color-functional-notation@7.0.12(postcss@8.5.15): dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.12) - '@csstools/utilities': 2.0.0(postcss@8.5.12) - postcss: 8.5.12 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.15) + '@csstools/utilities': 2.0.0(postcss@8.5.15) + postcss: 8.5.15 - postcss-color-hex-alpha@10.0.0(postcss@8.5.12): + postcss-color-hex-alpha@10.0.0(postcss@8.5.15): dependencies: - '@csstools/utilities': 2.0.0(postcss@8.5.12) - postcss: 8.5.12 + '@csstools/utilities': 2.0.0(postcss@8.5.15) + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-color-rebeccapurple@10.0.0(postcss@8.5.12): + postcss-color-rebeccapurple@10.0.0(postcss@8.5.15): dependencies: - '@csstools/utilities': 2.0.0(postcss@8.5.12) - postcss: 8.5.12 + '@csstools/utilities': 2.0.0(postcss@8.5.15) + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-colormin@6.1.0(postcss@8.5.12): + postcss-colormin@6.1.0(postcss@8.5.15): dependencies: browserslist: 4.28.2 caniuse-api: 3.0.0 colord: 2.9.3 - postcss: 8.5.12 + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-convert-values@6.1.0(postcss@8.5.12): + postcss-convert-values@6.1.0(postcss@8.5.15): dependencies: browserslist: 4.28.2 - postcss: 8.5.12 + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-custom-media@11.0.6(postcss@8.5.12): + postcss-custom-media@11.0.6(postcss@8.5.15): dependencies: '@csstools/cascade-layer-name-parser': 2.0.5(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 '@csstools/media-query-list-parser': 4.0.3(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) - postcss: 8.5.12 + postcss: 8.5.15 - postcss-custom-properties@14.0.6(postcss@8.5.12): + postcss-custom-properties@14.0.6(postcss@8.5.15): dependencies: '@csstools/cascade-layer-name-parser': 2.0.5(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/utilities': 2.0.0(postcss@8.5.12) - postcss: 8.5.12 + '@csstools/utilities': 2.0.0(postcss@8.5.15) + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-custom-selectors@8.0.5(postcss@8.5.12): + postcss-custom-selectors@8.0.5(postcss@8.5.15): dependencies: '@csstools/cascade-layer-name-parser': 2.0.5(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - postcss: 8.5.12 + postcss: 8.5.15 postcss-selector-parser: 7.1.1 - postcss-dir-pseudo-class@9.0.1(postcss@8.5.12): + postcss-dir-pseudo-class@9.0.1(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-selector-parser: 7.1.1 - postcss-discard-comments@6.0.2(postcss@8.5.12): + postcss-discard-comments@6.0.2(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 - postcss-discard-duplicates@6.0.3(postcss@8.5.12): + postcss-discard-duplicates@6.0.3(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 - postcss-discard-empty@6.0.3(postcss@8.5.12): + postcss-discard-empty@6.0.3(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 - postcss-discard-overridden@6.0.2(postcss@8.5.12): + postcss-discard-overridden@6.0.2(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 - postcss-discard-unused@6.0.5(postcss@8.5.12): + postcss-discard-unused@6.0.5(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-selector-parser: 6.1.2 - postcss-double-position-gradients@6.0.4(postcss@8.5.12): + postcss-double-position-gradients@6.0.4(postcss@8.5.15): dependencies: - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.12) - '@csstools/utilities': 2.0.0(postcss@8.5.12) - postcss: 8.5.12 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.15) + '@csstools/utilities': 2.0.0(postcss@8.5.15) + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-focus-visible@10.0.1(postcss@8.5.12): + postcss-focus-visible@10.0.1(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-selector-parser: 7.1.1 - postcss-focus-within@9.0.1(postcss@8.5.12): + postcss-focus-within@9.0.1(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-selector-parser: 7.1.1 - postcss-font-variant@5.0.0(postcss@8.5.12): + postcss-font-variant@5.0.0(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 - postcss-gap-properties@6.0.0(postcss@8.5.12): + postcss-gap-properties@6.0.0(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 - postcss-image-set-function@7.0.0(postcss@8.5.12): + postcss-image-set-function@7.0.0(postcss@8.5.15): dependencies: - '@csstools/utilities': 2.0.0(postcss@8.5.12) - postcss: 8.5.12 + '@csstools/utilities': 2.0.0(postcss@8.5.15) + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-import@15.1.0(postcss@8.5.12): + postcss-import@15.1.0(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-value-parser: 4.2.0 read-cache: 1.0.0 - resolve: 1.22.11 + resolve: 1.22.12 - postcss-js@4.1.0(postcss@8.5.12): + postcss-js@4.1.0(postcss@8.5.15): dependencies: camelcase-css: 2.0.1 - postcss: 8.5.12 + postcss: 8.5.15 - postcss-lab-function@7.0.12(postcss@8.5.12): + postcss-lab-function@7.0.12(postcss@8.5.15): dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.12) - '@csstools/utilities': 2.0.0(postcss@8.5.12) - postcss: 8.5.12 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.15) + '@csstools/utilities': 2.0.0(postcss@8.5.15) + postcss: 8.5.15 - postcss-load-config@3.1.4(postcss@8.5.12): + postcss-load-config@3.1.4(postcss@8.5.15): dependencies: lilconfig: 2.1.0 yaml: 1.10.3 optionalDependencies: - postcss: 8.5.12 + postcss: 8.5.15 - postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.12)(tsx@4.21.0)(yaml@2.8.3): + postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.15)(tsx@4.22.3)(yaml@2.9.0): dependencies: lilconfig: 3.1.3 optionalDependencies: jiti: 1.21.7 - postcss: 8.5.12 - tsx: 4.21.0 - yaml: 2.8.3 + postcss: 8.5.15 + tsx: 4.22.3 + yaml: 2.9.0 - postcss-loader@7.3.4(postcss@8.5.12)(typescript@6.0.3)(webpack@5.106.2): + postcss-loader@7.3.4(postcss@8.5.15)(typescript@6.0.3)(webpack@5.107.0(postcss@8.5.15)): dependencies: cosmiconfig: 8.3.6(typescript@6.0.3) jiti: 1.21.7 - postcss: 8.5.12 - semver: 7.7.4 - webpack: 5.106.2 + postcss: 8.5.15 + semver: 7.8.1 + webpack: 5.107.0(postcss@8.5.15) transitivePeerDependencies: - typescript - postcss-logical@8.1.0(postcss@8.5.12): + postcss-logical@8.1.0(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-merge-idents@6.0.3(postcss@8.5.12): + postcss-merge-idents@6.0.3(postcss@8.5.15): dependencies: - cssnano-utils: 4.0.2(postcss@8.5.12) - postcss: 8.5.12 + cssnano-utils: 4.0.2(postcss@8.5.15) + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-merge-longhand@6.0.5(postcss@8.5.12): + postcss-merge-longhand@6.0.5(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-value-parser: 4.2.0 - stylehacks: 6.1.1(postcss@8.5.12) + stylehacks: 6.1.1(postcss@8.5.15) - postcss-merge-rules@6.1.1(postcss@8.5.12): + postcss-merge-rules@6.1.1(postcss@8.5.15): dependencies: browserslist: 4.28.2 caniuse-api: 3.0.0 - cssnano-utils: 4.0.2(postcss@8.5.12) - postcss: 8.5.12 + cssnano-utils: 4.0.2(postcss@8.5.15) + postcss: 8.5.15 postcss-selector-parser: 6.1.2 - postcss-minify-font-values@6.1.0(postcss@8.5.12): + postcss-minify-font-values@6.1.0(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-minify-gradients@6.0.3(postcss@8.5.12): + postcss-minify-gradients@6.0.3(postcss@8.5.15): dependencies: colord: 2.9.3 - cssnano-utils: 4.0.2(postcss@8.5.12) - postcss: 8.5.12 + cssnano-utils: 4.0.2(postcss@8.5.15) + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-minify-params@6.1.0(postcss@8.5.12): + postcss-minify-params@6.1.0(postcss@8.5.15): dependencies: browserslist: 4.28.2 - cssnano-utils: 4.0.2(postcss@8.5.12) - postcss: 8.5.12 + cssnano-utils: 4.0.2(postcss@8.5.15) + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-minify-selectors@6.0.4(postcss@8.5.12): + postcss-minify-selectors@6.0.4(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-selector-parser: 6.1.2 - postcss-modules-extract-imports@3.1.0(postcss@8.5.12): + postcss-modules-extract-imports@3.1.0(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 - postcss-modules-local-by-default@4.2.0(postcss@8.5.12): + postcss-modules-local-by-default@4.2.0(postcss@8.5.15): dependencies: - icss-utils: 5.1.0(postcss@8.5.12) - postcss: 8.5.12 + icss-utils: 5.1.0(postcss@8.5.15) + postcss: 8.5.15 postcss-selector-parser: 7.1.1 postcss-value-parser: 4.2.0 - postcss-modules-scope@3.2.1(postcss@8.5.12): + postcss-modules-scope@3.2.1(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-selector-parser: 7.1.1 - postcss-modules-values@4.0.0(postcss@8.5.12): + postcss-modules-values@4.0.0(postcss@8.5.15): dependencies: - icss-utils: 5.1.0(postcss@8.5.12) - postcss: 8.5.12 + icss-utils: 5.1.0(postcss@8.5.15) + postcss: 8.5.15 - postcss-nested@6.2.0(postcss@8.5.12): + postcss-nested@6.2.0(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-selector-parser: 6.1.2 - postcss-nesting@13.0.2(postcss@8.5.12): + postcss-nesting@13.0.2(postcss@8.5.15): dependencies: '@csstools/selector-resolve-nested': 3.1.0(postcss-selector-parser@7.1.1) '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.1) - postcss: 8.5.12 + postcss: 8.5.15 postcss-selector-parser: 7.1.1 - postcss-normalize-charset@6.0.2(postcss@8.5.12): + postcss-normalize-charset@6.0.2(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 - postcss-normalize-display-values@6.0.2(postcss@8.5.12): + postcss-normalize-display-values@6.0.2(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-normalize-positions@6.0.2(postcss@8.5.12): + postcss-normalize-positions@6.0.2(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-normalize-repeat-style@6.0.2(postcss@8.5.12): + postcss-normalize-repeat-style@6.0.2(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-normalize-string@6.0.2(postcss@8.5.12): + postcss-normalize-string@6.0.2(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-normalize-timing-functions@6.0.2(postcss@8.5.12): + postcss-normalize-timing-functions@6.0.2(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-normalize-unicode@6.1.0(postcss@8.5.12): + postcss-normalize-unicode@6.1.0(postcss@8.5.15): dependencies: browserslist: 4.28.2 - postcss: 8.5.12 + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-normalize-url@6.0.2(postcss@8.5.12): + postcss-normalize-url@6.0.2(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-normalize-whitespace@6.0.2(postcss@8.5.12): + postcss-normalize-whitespace@6.0.2(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-opacity-percentage@3.0.0(postcss@8.5.12): + postcss-opacity-percentage@3.0.0(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 - postcss-ordered-values@6.0.2(postcss@8.5.12): + postcss-ordered-values@6.0.2(postcss@8.5.15): dependencies: - cssnano-utils: 4.0.2(postcss@8.5.12) - postcss: 8.5.12 + cssnano-utils: 4.0.2(postcss@8.5.15) + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-overflow-shorthand@6.0.0(postcss@8.5.12): + postcss-overflow-shorthand@6.0.0(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-page-break@3.0.4(postcss@8.5.12): + postcss-page-break@3.0.4(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 - postcss-place@10.0.0(postcss@8.5.12): + postcss-place@10.0.0(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-preset-env@10.5.0(postcss@8.5.12): + postcss-preset-env@10.6.1(postcss@8.5.15): dependencies: - '@csstools/postcss-alpha-function': 1.0.1(postcss@8.5.12) - '@csstools/postcss-cascade-layers': 5.0.2(postcss@8.5.12) - '@csstools/postcss-color-function': 4.0.12(postcss@8.5.12) - '@csstools/postcss-color-function-display-p3-linear': 1.0.1(postcss@8.5.12) - '@csstools/postcss-color-mix-function': 3.0.12(postcss@8.5.12) - '@csstools/postcss-color-mix-variadic-function-arguments': 1.0.2(postcss@8.5.12) - '@csstools/postcss-content-alt-text': 2.0.8(postcss@8.5.12) - '@csstools/postcss-contrast-color-function': 2.0.12(postcss@8.5.12) - '@csstools/postcss-exponential-functions': 2.0.9(postcss@8.5.12) - '@csstools/postcss-font-format-keywords': 4.0.0(postcss@8.5.12) - '@csstools/postcss-gamut-mapping': 2.0.11(postcss@8.5.12) - '@csstools/postcss-gradients-interpolation-method': 5.0.12(postcss@8.5.12) - '@csstools/postcss-hwb-function': 4.0.12(postcss@8.5.12) - '@csstools/postcss-ic-unit': 4.0.4(postcss@8.5.12) - '@csstools/postcss-initial': 2.0.1(postcss@8.5.12) - '@csstools/postcss-is-pseudo-class': 5.0.3(postcss@8.5.12) - '@csstools/postcss-light-dark-function': 2.0.11(postcss@8.5.12) - '@csstools/postcss-logical-float-and-clear': 3.0.0(postcss@8.5.12) - '@csstools/postcss-logical-overflow': 2.0.0(postcss@8.5.12) - '@csstools/postcss-logical-overscroll-behavior': 2.0.0(postcss@8.5.12) - '@csstools/postcss-logical-resize': 3.0.0(postcss@8.5.12) - '@csstools/postcss-logical-viewport-units': 3.0.4(postcss@8.5.12) - '@csstools/postcss-media-minmax': 2.0.9(postcss@8.5.12) - '@csstools/postcss-media-queries-aspect-ratio-number-values': 3.0.5(postcss@8.5.12) - '@csstools/postcss-nested-calc': 4.0.0(postcss@8.5.12) - '@csstools/postcss-normalize-display-values': 4.0.0(postcss@8.5.12) - '@csstools/postcss-oklab-function': 4.0.12(postcss@8.5.12) - '@csstools/postcss-position-area-property': 1.0.0(postcss@8.5.12) - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.12) - '@csstools/postcss-random-function': 2.0.1(postcss@8.5.12) - '@csstools/postcss-relative-color-syntax': 3.0.12(postcss@8.5.12) - '@csstools/postcss-scope-pseudo-class': 4.0.1(postcss@8.5.12) - '@csstools/postcss-sign-functions': 1.1.4(postcss@8.5.12) - '@csstools/postcss-stepped-value-functions': 4.0.9(postcss@8.5.12) - '@csstools/postcss-system-ui-font-family': 1.0.0(postcss@8.5.12) - '@csstools/postcss-text-decoration-shorthand': 4.0.3(postcss@8.5.12) - '@csstools/postcss-trigonometric-functions': 4.0.9(postcss@8.5.12) - '@csstools/postcss-unset-value': 4.0.0(postcss@8.5.12) - autoprefixer: 10.5.0(postcss@8.5.12) + '@csstools/postcss-alpha-function': 1.0.1(postcss@8.5.15) + '@csstools/postcss-cascade-layers': 5.0.2(postcss@8.5.15) + '@csstools/postcss-color-function': 4.0.12(postcss@8.5.15) + '@csstools/postcss-color-function-display-p3-linear': 1.0.1(postcss@8.5.15) + '@csstools/postcss-color-mix-function': 3.0.12(postcss@8.5.15) + '@csstools/postcss-color-mix-variadic-function-arguments': 1.0.2(postcss@8.5.15) + '@csstools/postcss-content-alt-text': 2.0.8(postcss@8.5.15) + '@csstools/postcss-contrast-color-function': 2.0.12(postcss@8.5.15) + '@csstools/postcss-exponential-functions': 2.0.9(postcss@8.5.15) + '@csstools/postcss-font-format-keywords': 4.0.0(postcss@8.5.15) + '@csstools/postcss-gamut-mapping': 2.0.11(postcss@8.5.15) + '@csstools/postcss-gradients-interpolation-method': 5.0.12(postcss@8.5.15) + '@csstools/postcss-hwb-function': 4.0.12(postcss@8.5.15) + '@csstools/postcss-ic-unit': 4.0.4(postcss@8.5.15) + '@csstools/postcss-initial': 2.0.1(postcss@8.5.15) + '@csstools/postcss-is-pseudo-class': 5.0.3(postcss@8.5.15) + '@csstools/postcss-light-dark-function': 2.0.11(postcss@8.5.15) + '@csstools/postcss-logical-float-and-clear': 3.0.0(postcss@8.5.15) + '@csstools/postcss-logical-overflow': 2.0.0(postcss@8.5.15) + '@csstools/postcss-logical-overscroll-behavior': 2.0.0(postcss@8.5.15) + '@csstools/postcss-logical-resize': 3.0.0(postcss@8.5.15) + '@csstools/postcss-logical-viewport-units': 3.0.4(postcss@8.5.15) + '@csstools/postcss-media-minmax': 2.0.9(postcss@8.5.15) + '@csstools/postcss-media-queries-aspect-ratio-number-values': 3.0.5(postcss@8.5.15) + '@csstools/postcss-nested-calc': 4.0.0(postcss@8.5.15) + '@csstools/postcss-normalize-display-values': 4.0.1(postcss@8.5.15) + '@csstools/postcss-oklab-function': 4.0.12(postcss@8.5.15) + '@csstools/postcss-position-area-property': 1.0.0(postcss@8.5.15) + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.15) + '@csstools/postcss-property-rule-prelude-list': 1.0.0(postcss@8.5.15) + '@csstools/postcss-random-function': 2.0.1(postcss@8.5.15) + '@csstools/postcss-relative-color-syntax': 3.0.12(postcss@8.5.15) + '@csstools/postcss-scope-pseudo-class': 4.0.1(postcss@8.5.15) + '@csstools/postcss-sign-functions': 1.1.4(postcss@8.5.15) + '@csstools/postcss-stepped-value-functions': 4.0.9(postcss@8.5.15) + '@csstools/postcss-syntax-descriptor-syntax-production': 1.0.1(postcss@8.5.15) + '@csstools/postcss-system-ui-font-family': 1.0.0(postcss@8.5.15) + '@csstools/postcss-text-decoration-shorthand': 4.0.3(postcss@8.5.15) + '@csstools/postcss-trigonometric-functions': 4.0.9(postcss@8.5.15) + '@csstools/postcss-unset-value': 4.0.0(postcss@8.5.15) + autoprefixer: 10.5.0(postcss@8.5.15) browserslist: 4.28.2 - css-blank-pseudo: 7.0.1(postcss@8.5.12) - css-has-pseudo: 7.0.3(postcss@8.5.12) - css-prefers-color-scheme: 10.0.0(postcss@8.5.12) - cssdb: 8.5.2 - postcss: 8.5.12 - postcss-attribute-case-insensitive: 7.0.1(postcss@8.5.12) - postcss-clamp: 4.1.0(postcss@8.5.12) - postcss-color-functional-notation: 7.0.12(postcss@8.5.12) - postcss-color-hex-alpha: 10.0.0(postcss@8.5.12) - postcss-color-rebeccapurple: 10.0.0(postcss@8.5.12) - postcss-custom-media: 11.0.6(postcss@8.5.12) - postcss-custom-properties: 14.0.6(postcss@8.5.12) - postcss-custom-selectors: 8.0.5(postcss@8.5.12) - postcss-dir-pseudo-class: 9.0.1(postcss@8.5.12) - postcss-double-position-gradients: 6.0.4(postcss@8.5.12) - postcss-focus-visible: 10.0.1(postcss@8.5.12) - postcss-focus-within: 9.0.1(postcss@8.5.12) - postcss-font-variant: 5.0.0(postcss@8.5.12) - postcss-gap-properties: 6.0.0(postcss@8.5.12) - postcss-image-set-function: 7.0.0(postcss@8.5.12) - postcss-lab-function: 7.0.12(postcss@8.5.12) - postcss-logical: 8.1.0(postcss@8.5.12) - postcss-nesting: 13.0.2(postcss@8.5.12) - postcss-opacity-percentage: 3.0.0(postcss@8.5.12) - postcss-overflow-shorthand: 6.0.0(postcss@8.5.12) - postcss-page-break: 3.0.4(postcss@8.5.12) - postcss-place: 10.0.0(postcss@8.5.12) - postcss-pseudo-class-any-link: 10.0.1(postcss@8.5.12) - postcss-replace-overflow-wrap: 4.0.0(postcss@8.5.12) - postcss-selector-not: 8.0.1(postcss@8.5.12) + css-blank-pseudo: 7.0.1(postcss@8.5.15) + css-has-pseudo: 7.0.3(postcss@8.5.15) + css-prefers-color-scheme: 10.0.0(postcss@8.5.15) + cssdb: 8.9.0 + postcss: 8.5.15 + postcss-attribute-case-insensitive: 7.0.1(postcss@8.5.15) + postcss-clamp: 4.1.0(postcss@8.5.15) + postcss-color-functional-notation: 7.0.12(postcss@8.5.15) + postcss-color-hex-alpha: 10.0.0(postcss@8.5.15) + postcss-color-rebeccapurple: 10.0.0(postcss@8.5.15) + postcss-custom-media: 11.0.6(postcss@8.5.15) + postcss-custom-properties: 14.0.6(postcss@8.5.15) + postcss-custom-selectors: 8.0.5(postcss@8.5.15) + postcss-dir-pseudo-class: 9.0.1(postcss@8.5.15) + postcss-double-position-gradients: 6.0.4(postcss@8.5.15) + postcss-focus-visible: 10.0.1(postcss@8.5.15) + postcss-focus-within: 9.0.1(postcss@8.5.15) + postcss-font-variant: 5.0.0(postcss@8.5.15) + postcss-gap-properties: 6.0.0(postcss@8.5.15) + postcss-image-set-function: 7.0.0(postcss@8.5.15) + postcss-lab-function: 7.0.12(postcss@8.5.15) + postcss-logical: 8.1.0(postcss@8.5.15) + postcss-nesting: 13.0.2(postcss@8.5.15) + postcss-opacity-percentage: 3.0.0(postcss@8.5.15) + postcss-overflow-shorthand: 6.0.0(postcss@8.5.15) + postcss-page-break: 3.0.4(postcss@8.5.15) + postcss-place: 10.0.0(postcss@8.5.15) + postcss-pseudo-class-any-link: 10.0.1(postcss@8.5.15) + postcss-replace-overflow-wrap: 4.0.0(postcss@8.5.15) + postcss-selector-not: 8.0.1(postcss@8.5.15) - postcss-pseudo-class-any-link@10.0.1(postcss@8.5.12): + postcss-pseudo-class-any-link@10.0.1(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-selector-parser: 7.1.1 - postcss-reduce-idents@6.0.3(postcss@8.5.12): + postcss-reduce-idents@6.0.3(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-reduce-initial@6.1.0(postcss@8.5.12): + postcss-reduce-initial@6.1.0(postcss@8.5.15): dependencies: browserslist: 4.28.2 caniuse-api: 3.0.0 - postcss: 8.5.12 + postcss: 8.5.15 - postcss-reduce-transforms@6.0.2(postcss@8.5.12): + postcss-reduce-transforms@6.0.2(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-replace-overflow-wrap@4.0.0(postcss@8.5.12): + postcss-replace-overflow-wrap@4.0.0(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 - postcss-safe-parser@7.0.1(postcss@8.5.12): + postcss-safe-parser@7.0.1(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 - postcss-scss@4.0.9(postcss@8.5.12): + postcss-scss@4.0.9(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 - postcss-selector-not@8.0.1(postcss@8.5.12): + postcss-selector-not@8.0.1(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-selector-parser: 7.1.1 postcss-selector-parser@6.1.2: @@ -23761,31 +24162,31 @@ snapshots: cssesc: 3.0.0 util-deprecate: 1.0.2 - postcss-sort-media-queries@5.2.0(postcss@8.5.12): + postcss-sort-media-queries@5.2.0(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 sort-css-media-queries: 2.2.0 - postcss-svgo@6.0.3(postcss@8.5.12): + postcss-svgo@6.0.3(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-value-parser: 4.2.0 - svgo: 3.3.2 + svgo: 3.3.3 - postcss-unique-selectors@6.0.4(postcss@8.5.12): + postcss-unique-selectors@6.0.4(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 postcss-selector-parser: 6.1.2 postcss-value-parser@4.2.0: {} - postcss-zindex@6.0.2(postcss@8.5.12): + postcss-zindex@6.0.2(postcss@8.5.15): dependencies: - postcss: 8.5.12 + postcss: 8.5.15 - postcss@8.5.12: + postcss@8.5.15: dependencies: - nanoid: 3.3.11 + nanoid: 3.3.12 picocolors: 1.1.1 source-map-js: 1.2.1 @@ -23805,6 +24206,22 @@ snapshots: powershell-utils@0.1.0: {} + prebuild-install@7.1.3: + dependencies: + detect-libc: 2.1.2 + expand-template: 2.0.3 + github-from-package: 0.0.0 + minimist: 1.2.8 + mkdirp-classic: 0.5.3 + napi-build-utils: 2.0.0 + node-abi: 3.92.0 + pump: 3.0.4 + rc: 1.2.8 + simple-get: 4.0.1 + tar-fs: 2.1.4 + tunnel-agent: 0.6.0 + optional: true + prelude-ls@1.2.1: {} prettier-linter-helpers@1.0.1: @@ -23820,10 +24237,10 @@ snapshots: dependencies: prettier: 3.8.3 - prettier-plugin-svelte@3.5.1(prettier@3.8.3)(svelte@5.55.2): + prettier-plugin-svelte@4.1.0(prettier@3.8.3)(svelte@5.55.8(@typescript-eslint/types@8.59.4)): dependencies: prettier: 3.8.3 - svelte: 5.55.2 + svelte: 5.55.8(@typescript-eslint/types@8.59.4) prettier@3.8.3: {} @@ -23840,11 +24257,11 @@ snapshots: pretty-time@1.1.0: {} - prism-react-renderer@2.4.1(react@19.2.5): + prism-react-renderer@2.4.1(react@19.2.6): dependencies: - '@types/prismjs': 1.26.5 + '@types/prismjs': 1.26.6 clsx: 2.1.1 - react: 19.2.5 + react: 19.2.6 prismjs@1.30.0: {} @@ -23854,11 +24271,6 @@ snapshots: process@0.11.10: {} - promise-retry@2.0.1: - dependencies: - err-code: 2.0.3 - retry: 0.12.0 - prompts@2.4.2: dependencies: kleur: 3.0.3 @@ -23891,34 +24303,19 @@ snapshots: proto-list@1.2.4: {} - protobufjs@7.5.5: + protobufjs@7.6.0: dependencies: '@protobufjs/aspromise': 1.1.2 '@protobufjs/base64': 1.1.2 - '@protobufjs/codegen': 2.0.4 + '@protobufjs/codegen': 2.0.5 '@protobufjs/eventemitter': 1.1.0 - '@protobufjs/fetch': 1.1.0 + '@protobufjs/fetch': 1.1.1 '@protobufjs/float': 1.0.2 - '@protobufjs/inquire': 1.1.0 + '@protobufjs/inquire': 1.1.2 '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 - '@protobufjs/utf8': 1.1.0 - '@types/node': 24.12.2 - long: 5.3.2 - - protobufjs@8.0.1: - dependencies: - '@protobufjs/aspromise': 1.1.2 - '@protobufjs/base64': 1.1.2 - '@protobufjs/codegen': 2.0.4 - '@protobufjs/eventemitter': 1.1.0 - '@protobufjs/fetch': 1.1.0 - '@protobufjs/float': 1.0.2 - '@protobufjs/inquire': 1.1.0 - '@protobufjs/path': 1.1.2 - '@protobufjs/pool': 1.1.0 - '@protobufjs/utf8': 1.1.0 - '@types/node': 24.12.2 + '@protobufjs/utf8': 1.1.1 + '@types/node': 24.12.4 long: 5.3.2 protocol-buffers-schema@3.6.1: {} @@ -23941,16 +24338,24 @@ snapshots: dependencies: escape-goat: 4.0.0 + pvtsutils@1.3.6: + dependencies: + tslib: 2.8.1 + + pvutils@1.1.5: {} + qrcode@1.5.4: dependencies: dijkstrajs: 1.0.3 pngjs: 5.0.0 yargs: 15.4.1 - qs@6.14.1: + qs@6.15.2: dependencies: side-channel: 1.1.0 + queue-lit@1.5.2: {} + queue-microtask@1.2.3: {} quick-lru@5.1.1: {} @@ -23988,11 +24393,11 @@ snapshots: iconv-lite: 0.7.2 unpipe: 1.0.0 - raw-loader@4.0.2(webpack@5.106.2): + raw-loader@4.0.2(webpack@5.107.0(postcss@8.5.15)): dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 - webpack: 5.106.2 + webpack: 5.107.0(postcss@8.5.15) rc@1.2.8: dependencies: @@ -24001,9 +24406,9 @@ snapshots: minimist: 1.2.8 strip-json-comments: 2.0.1 - react-dom@19.2.5(react@19.2.5): + react-dom@19.2.6(react@19.2.6): dependencies: - react: 19.2.5 + react: 19.2.6 scheduler: 0.27.0 react-email@5.2.11: @@ -24036,47 +24441,47 @@ snapshots: react-is@17.0.2: {} - react-json-view-lite@2.5.0(react@19.2.5): + react-json-view-lite@2.5.0(react@19.2.6): dependencies: - react: 19.2.5 + react: 19.2.6 - react-loadable-ssr-addon-v5-slorber@1.0.3(@docusaurus/react-loadable@6.0.0(react@19.2.5))(webpack@5.106.2): + react-loadable-ssr-addon-v5-slorber@1.0.3(@docusaurus/react-loadable@6.0.0(react@19.2.6))(webpack@5.107.0(postcss@8.5.15)): dependencies: - '@babel/runtime': 7.29.2 - react-loadable: '@docusaurus/react-loadable@6.0.0(react@19.2.5)' - webpack: 5.106.2 + '@babel/runtime': 7.29.7 + react-loadable: '@docusaurus/react-loadable@6.0.0(react@19.2.6)' + webpack: 5.107.0(postcss@8.5.15) - react-router-config@5.1.1(react-router@5.3.4(react@19.2.5))(react@19.2.5): + react-router-config@5.1.1(react-router@5.3.4(react@19.2.6))(react@19.2.6): dependencies: - '@babel/runtime': 7.29.2 - react: 19.2.5 - react-router: 5.3.4(react@19.2.5) + '@babel/runtime': 7.29.7 + react: 19.2.6 + react-router: 5.3.4(react@19.2.6) - react-router-dom@5.3.4(react@19.2.5): + react-router-dom@5.3.4(react@19.2.6): dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 history: 4.10.1 loose-envify: 1.4.0 prop-types: 15.8.1 - react: 19.2.5 - react-router: 5.3.4(react@19.2.5) + react: 19.2.6 + react-router: 5.3.4(react@19.2.6) tiny-invariant: 1.3.3 tiny-warning: 1.0.3 - react-router@5.3.4(react@19.2.5): + react-router@5.3.4(react@19.2.6): dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 history: 4.10.1 hoist-non-react-statics: 3.3.2 loose-envify: 1.4.0 path-to-regexp: 1.9.0 prop-types: 15.8.1 - react: 19.2.5 + react: 19.2.6 react-is: 16.13.1 tiny-invariant: 1.3.3 tiny-warning: 1.0.3 - react@19.2.5: {} + react@19.2.6: {} read-cache@1.0.0: dependencies: @@ -24118,7 +24523,7 @@ snapshots: recma-build-jsx@1.0.0: dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 estree-util-build-jsx: 3.0.1 vfile: 6.0.3 @@ -24133,14 +24538,14 @@ snapshots: recma-parse@1.0.0: dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 esast-util-from-js: 2.0.1 unified: 11.0.5 vfile: 6.0.3 recma-stringify@1.0.0: dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 estree-util-to-js: 2.0.0 unified: 11.0.5 vfile: 6.0.3 @@ -24171,13 +24576,13 @@ snapshots: regenerate: 1.4.2 regenerate-unicode-properties: 10.2.2 regjsgen: 0.8.0 - regjsparser: 0.13.0 + regjsparser: 0.13.1 unicode-match-property-ecmascript: 2.0.0 unicode-match-property-value-ecmascript: 2.2.1 - registry-auth-token@5.1.0: + registry-auth-token@5.1.1: dependencies: - '@pnpm/npm-conf': 2.3.1 + '@pnpm/npm-conf': 3.0.2 registry-url@6.0.1: dependencies: @@ -24185,7 +24590,7 @@ snapshots: regjsgen@0.8.0: {} - regjsparser@0.13.0: + regjsparser@0.13.1: dependencies: jsesc: 3.1.0 @@ -24202,7 +24607,7 @@ snapshots: rehype-recma@1.0.0: dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 '@types/hast': 3.0.4 hast-util-to-estree: 3.1.3 transitivePeerDependencies: @@ -24257,7 +24662,7 @@ snapshots: remark-parse@11.0.0: dependencies: '@types/mdast': 4.0.4 - mdast-util-from-markdown: 2.0.2 + mdast-util-from-markdown: 2.0.3 micromark-util-types: 2.0.2 unified: 11.0.5 transitivePeerDependencies: @@ -24316,17 +24721,13 @@ snapshots: dependencies: protocol-buffers-schema: 3.6.1 - resolve@1.22.11: + resolve@1.22.12: dependencies: - is-core-module: 2.16.1 + es-errors: 1.3.0 + is-core-module: 2.16.2 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - response-time@2.3.4: - dependencies: - depd: 2.0.0 - on-headers: 1.1.0 - responselike@3.0.0: dependencies: lowercase-keys: 3.0.0 @@ -24355,66 +24756,66 @@ snapshots: robust-predicates@3.0.3: {} - rolldown@1.0.0-rc.17: + rolldown@1.0.1: dependencies: - '@oxc-project/types': 0.127.0 - '@rolldown/pluginutils': 1.0.0-rc.17 + '@oxc-project/types': 0.130.0 + '@rolldown/pluginutils': 1.0.1 optionalDependencies: - '@rolldown/binding-android-arm64': 1.0.0-rc.17 - '@rolldown/binding-darwin-arm64': 1.0.0-rc.17 - '@rolldown/binding-darwin-x64': 1.0.0-rc.17 - '@rolldown/binding-freebsd-x64': 1.0.0-rc.17 - '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.17 - '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.17 - '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.17 - '@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.17 - '@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.17 - '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.17 - '@rolldown/binding-linux-x64-musl': 1.0.0-rc.17 - '@rolldown/binding-openharmony-arm64': 1.0.0-rc.17 - '@rolldown/binding-wasm32-wasi': 1.0.0-rc.17 - '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.17 - '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.17 + '@rolldown/binding-android-arm64': 1.0.1 + '@rolldown/binding-darwin-arm64': 1.0.1 + '@rolldown/binding-darwin-x64': 1.0.1 + '@rolldown/binding-freebsd-x64': 1.0.1 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.1 + '@rolldown/binding-linux-arm64-gnu': 1.0.1 + '@rolldown/binding-linux-arm64-musl': 1.0.1 + '@rolldown/binding-linux-ppc64-gnu': 1.0.1 + '@rolldown/binding-linux-s390x-gnu': 1.0.1 + '@rolldown/binding-linux-x64-gnu': 1.0.1 + '@rolldown/binding-linux-x64-musl': 1.0.1 + '@rolldown/binding-openharmony-arm64': 1.0.1 + '@rolldown/binding-wasm32-wasi': 1.0.1 + '@rolldown/binding-win32-arm64-msvc': 1.0.1 + '@rolldown/binding-win32-x64-msvc': 1.0.1 - rollup-plugin-visualizer@7.0.1(rolldown@1.0.0-rc.17)(rollup@4.55.1): + rollup-plugin-visualizer@7.0.1(rolldown@1.0.1)(rollup@4.60.4): dependencies: open: 11.0.0 picomatch: 4.0.4 source-map: 0.7.6 yargs: 18.0.0 optionalDependencies: - rolldown: 1.0.0-rc.17 - rollup: 4.55.1 + rolldown: 1.0.1 + rollup: 4.60.4 - rollup@4.55.1: + rollup@4.60.4: dependencies: '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.55.1 - '@rollup/rollup-android-arm64': 4.55.1 - '@rollup/rollup-darwin-arm64': 4.55.1 - '@rollup/rollup-darwin-x64': 4.55.1 - '@rollup/rollup-freebsd-arm64': 4.55.1 - '@rollup/rollup-freebsd-x64': 4.55.1 - '@rollup/rollup-linux-arm-gnueabihf': 4.55.1 - '@rollup/rollup-linux-arm-musleabihf': 4.55.1 - '@rollup/rollup-linux-arm64-gnu': 4.55.1 - '@rollup/rollup-linux-arm64-musl': 4.55.1 - '@rollup/rollup-linux-loong64-gnu': 4.55.1 - '@rollup/rollup-linux-loong64-musl': 4.55.1 - '@rollup/rollup-linux-ppc64-gnu': 4.55.1 - '@rollup/rollup-linux-ppc64-musl': 4.55.1 - '@rollup/rollup-linux-riscv64-gnu': 4.55.1 - '@rollup/rollup-linux-riscv64-musl': 4.55.1 - '@rollup/rollup-linux-s390x-gnu': 4.55.1 - '@rollup/rollup-linux-x64-gnu': 4.55.1 - '@rollup/rollup-linux-x64-musl': 4.55.1 - '@rollup/rollup-openbsd-x64': 4.55.1 - '@rollup/rollup-openharmony-arm64': 4.55.1 - '@rollup/rollup-win32-arm64-msvc': 4.55.1 - '@rollup/rollup-win32-ia32-msvc': 4.55.1 - '@rollup/rollup-win32-x64-gnu': 4.55.1 - '@rollup/rollup-win32-x64-msvc': 4.55.1 + '@rollup/rollup-android-arm-eabi': 4.60.4 + '@rollup/rollup-android-arm64': 4.60.4 + '@rollup/rollup-darwin-arm64': 4.60.4 + '@rollup/rollup-darwin-x64': 4.60.4 + '@rollup/rollup-freebsd-arm64': 4.60.4 + '@rollup/rollup-freebsd-x64': 4.60.4 + '@rollup/rollup-linux-arm-gnueabihf': 4.60.4 + '@rollup/rollup-linux-arm-musleabihf': 4.60.4 + '@rollup/rollup-linux-arm64-gnu': 4.60.4 + '@rollup/rollup-linux-arm64-musl': 4.60.4 + '@rollup/rollup-linux-loong64-gnu': 4.60.4 + '@rollup/rollup-linux-loong64-musl': 4.60.4 + '@rollup/rollup-linux-ppc64-gnu': 4.60.4 + '@rollup/rollup-linux-ppc64-musl': 4.60.4 + '@rollup/rollup-linux-riscv64-gnu': 4.60.4 + '@rollup/rollup-linux-riscv64-musl': 4.60.4 + '@rollup/rollup-linux-s390x-gnu': 4.60.4 + '@rollup/rollup-linux-x64-gnu': 4.60.4 + '@rollup/rollup-linux-x64-musl': 4.60.4 + '@rollup/rollup-openbsd-x64': 4.60.4 + '@rollup/rollup-openharmony-arm64': 4.60.4 + '@rollup/rollup-win32-arm64-msvc': 4.60.4 + '@rollup/rollup-win32-ia32-msvc': 4.60.4 + '@rollup/rollup-win32-x64-gnu': 4.60.4 + '@rollup/rollup-win32-x64-msvc': 4.60.4 fsevents: 2.3.3 roughjs@4.6.6: @@ -24441,7 +24842,7 @@ snapshots: dependencies: escalade: 3.2.0 picocolors: 1.1.1 - postcss: 8.5.12 + postcss: 8.5.15 strip-json-comments: 3.1.1 run-applescript@7.1.0: {} @@ -24452,14 +24853,14 @@ snapshots: dependencies: queue-microtask: 1.2.3 - runed@0.35.1(@sveltejs/kit@2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2): + runed@0.35.1(@sveltejs/kit@2.60.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.55.8(@typescript-eslint/types@8.59.4))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4))(typescript@6.0.3)(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4)): dependencies: dequal: 2.0.3 esm-env: 1.2.2 lz-string: 1.5.0 - svelte: 5.55.2 + svelte: 5.55.8(@typescript-eslint/types@8.59.4) optionalDependencies: - '@sveltejs/kit': 2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + '@sveltejs/kit': 2.60.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.55.8(@typescript-eslint/types@8.59.4))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4))(typescript@6.0.3)(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)) rw@1.3.3: {} @@ -24493,7 +24894,7 @@ snapshots: optionalDependencies: '@parcel/watcher': 2.5.6 - sax@1.4.3: {} + sax@1.6.0: {} saxes@6.0.0: dependencies: @@ -24507,15 +24908,15 @@ snapshots: schema-utils@3.3.0: dependencies: '@types/json-schema': 7.0.15 - ajv: 6.14.0 - ajv-keywords: 3.5.2(ajv@6.14.0) + ajv: 6.15.0 + ajv-keywords: 3.5.2(ajv@6.15.0) schema-utils@4.3.3: dependencies: '@types/json-schema': 7.0.15 - ajv: 8.18.0 - ajv-formats: 2.1.1(ajv@8.18.0) - ajv-keywords: 5.1.0(ajv@8.18.0) + ajv: 8.20.0 + ajv-formats: 2.1.1(ajv@8.20.0) + ajv-keywords: 5.1.0(ajv@8.20.0) search-insights@2.17.3: {} @@ -24530,18 +24931,20 @@ snapshots: select-hose@2.0.0: {} - selfsigned@2.4.1: + selfsigned@5.5.0: dependencies: - '@types/node-forge': 1.3.14 - node-forge: 1.3.3 + '@peculiar/x509': 1.14.3 + pkijs: 3.4.0 semver-diff@4.0.0: dependencies: - semver: 7.7.4 + semver: 7.8.1 semver@6.3.1: {} - semver@7.7.4: {} + semver@7.8.0: {} + + semver@7.8.1: {} send@0.19.2: dependencies: @@ -24591,13 +24994,13 @@ snapshots: path-to-regexp: 3.3.0 range-parser: 1.2.0 - serve-index@1.9.1: + serve-index@1.9.2: dependencies: accepts: 1.3.8 batch: 0.6.1 debug: 2.6.9 escape-html: 1.0.3 - http-errors: 1.6.3 + http-errors: 1.8.1 mime-types: 2.1.35 parseurl: 1.3.3 transitivePeerDependencies: @@ -24634,8 +25037,6 @@ snapshots: gopd: 1.2.0 has-property-descriptors: 1.0.2 - setprototypeof@1.1.0: {} - setprototypeof@1.2.0: {} shallow-clone@3.0.1: @@ -24655,11 +25056,11 @@ snapshots: sharp@0.34.5: dependencies: - '@img/colour': 1.0.0 + '@img/colour': 1.1.0 detect-libc: 2.1.2 - node-addon-api: 8.5.0 - node-gyp: 12.2.0 - semver: 7.7.4 + node-addon-api: 8.7.0 + node-gyp: 12.3.0 + semver: 7.8.1 optionalDependencies: '@img/sharp-darwin-arm64': 0.34.5 '@img/sharp-darwin-x64': 0.34.5 @@ -24685,8 +25086,6 @@ snapshots: '@img/sharp-win32-arm64': 0.34.5 '@img/sharp-win32-ia32': 0.34.5 '@img/sharp-win32-x64': 0.34.5 - transitivePeerDependencies: - - supports-color shebang-command@2.0.0: dependencies: @@ -24696,7 +25095,7 @@ snapshots: shell-quote@1.8.3: {} - side-channel-list@1.0.0: + side-channel-list@1.0.1: dependencies: es-errors: 1.3.0 object-inspect: 1.13.4 @@ -24720,7 +25119,7 @@ snapshots: dependencies: es-errors: 1.3.0 object-inspect: 1.13.4 - side-channel-list: 1.0.0 + side-channel-list: 1.0.1 side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 @@ -24733,14 +25132,14 @@ snapshots: simple-concat@1.0.1: optional: true - simple-get@3.1.1: + simple-get@4.0.1: dependencies: - decompress-response: 4.2.1 + decompress-response: 6.0.0 once: 1.4.0 simple-concat: 1.0.1 optional: true - simple-icons@16.17.0: {} + simple-icons@16.20.0: {} sirv@2.0.4: dependencies: @@ -24756,12 +25155,12 @@ snapshots: sisteransi@1.0.5: {} - sitemap@7.1.2: + sitemap@7.1.3: dependencies: '@types/node': 17.0.45 '@types/sax': 1.2.7 arg: 5.0.2 - sax: 1.4.3 + sax: 1.6.0 skin-tone@2.0.0: dependencies: @@ -24773,17 +25172,15 @@ snapshots: slice-source@0.4.1: {} - smart-buffer@4.2.0: {} - snake-case@3.0.4: dependencies: dot-case: 3.0.4 tslib: 2.8.1 - socket.io-adapter@2.5.6: + socket.io-adapter@2.5.7: dependencies: debug: 4.4.3 - ws: 8.18.3 + ws: 8.20.1 transitivePeerDependencies: - bufferutil - supports-color @@ -24793,14 +25190,14 @@ snapshots: dependencies: '@socket.io/component-emitter': 3.1.2 debug: 4.4.3 - engine.io-client: 6.6.4 - socket.io-parser: 4.2.5 + engine.io-client: 6.6.5 + socket.io-parser: 4.2.6 transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate - socket.io-parser@4.2.5: + socket.io-parser@4.2.6: dependencies: '@socket.io/component-emitter': 3.1.2 debug: 4.4.3 @@ -24813,9 +25210,9 @@ snapshots: base64id: 2.0.0 cors: 2.8.6 debug: 4.4.3 - engine.io: 6.6.5 - socket.io-adapter: 2.5.6 - socket.io-parser: 4.2.5 + engine.io: 6.6.8 + socket.io-adapter: 2.5.7 + socket.io-parser: 4.2.6 transitivePeerDependencies: - bufferutil - supports-color @@ -24827,19 +25224,6 @@ snapshots: uuid: 8.3.2 websocket-driver: 0.7.4 - socks-proxy-agent@8.0.5: - dependencies: - agent-base: 7.1.4 - debug: 4.4.3 - socks: 2.8.7 - transitivePeerDependencies: - - supports-color - - socks@2.8.7: - dependencies: - ip-address: 10.1.0 - smart-buffer: 4.2.0 - sort-css-media-queries@2.2.0: {} source-map-js@1.2.1: {} @@ -24886,7 +25270,7 @@ snapshots: sprintf-js@1.0.3: {} - sql-formatter@15.7.3: + sql-formatter@15.8.0: dependencies: argparse: 2.0.1 nearley: 2.20.1 @@ -24904,11 +25288,7 @@ snapshots: bcrypt-pbkdf: 1.0.2 optionalDependencies: cpu-features: 0.0.10 - nan: 2.26.2 - - ssri@13.0.1: - dependencies: - minipass: 7.1.3 + nan: 2.27.0 stackback@0.0.2: {} @@ -24952,7 +25332,7 @@ snapshots: string-width@7.2.0: dependencies: emoji-regex: 10.6.0 - get-east-asian-width: 1.5.0 + get-east-asian-width: 1.6.0 strip-ansi: 7.2.0 string_decoder@1.1.1: @@ -25022,13 +25402,13 @@ snapshots: dependencies: inline-style-parser: 0.2.7 - stylehacks@6.1.1(postcss@8.5.12): + stylehacks@6.1.1(postcss@8.5.15): dependencies: browserslist: 4.28.2 - postcss: 8.5.12 + postcss: 8.5.15 postcss-selector-parser: 6.1.2 - stylis@4.3.6: {} + stylis@4.4.0: {} sucrase@3.35.1: dependencies: @@ -25050,13 +25430,13 @@ snapshots: formidable: 3.5.4 methods: 1.1.2 mime: 2.6.0 - qs: 6.14.1 + qs: 6.15.2 transitivePeerDependencies: - supports-color supercluster@8.0.1: dependencies: - kdbush: 4.0.2 + kdbush: 4.1.0 supertest@7.2.2: dependencies: @@ -25076,33 +25456,33 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - svelte-awesome@3.3.5(svelte@5.55.2): + svelte-awesome@3.3.5(svelte@5.55.8(@typescript-eslint/types@8.59.4)): dependencies: - svelte: 5.55.2 + svelte: 5.55.8(@typescript-eslint/types@8.59.4) - svelte-check@4.4.6(picomatch@4.0.4)(svelte@5.55.2)(typescript@6.0.3): + svelte-check@4.4.8(picomatch@4.0.4)(svelte@5.55.8(@typescript-eslint/types@8.59.4))(typescript@6.0.3): dependencies: '@jridgewell/trace-mapping': 0.3.31 chokidar: 4.0.3 fdir: 6.5.0(picomatch@4.0.4) picocolors: 1.1.1 sade: 1.8.1 - svelte: 5.55.2 + svelte: 5.55.8(@typescript-eslint/types@8.59.4) typescript: 6.0.3 transitivePeerDependencies: - picomatch - svelte-eslint-parser@1.6.0(svelte@5.55.2): + svelte-eslint-parser@1.6.1(svelte@5.55.8(@typescript-eslint/types@8.59.4)): dependencies: eslint-scope: 8.4.0 eslint-visitor-keys: 4.2.1 espree: 10.4.0 - postcss: 8.5.12 - postcss-scss: 4.0.9(postcss@8.5.12) + postcss: 8.5.15 + postcss-scss: 4.0.9(postcss@8.5.15) postcss-selector-parser: 7.1.1 - semver: 7.7.4 + semver: 7.8.1 optionalDependencies: - svelte: 5.55.2 + svelte: 5.55.8(@typescript-eslint/types@8.59.4) svelte-floating-ui@1.5.8: dependencies: @@ -25115,7 +25495,7 @@ snapshots: dependencies: highlight.js: 11.11.1 - svelte-i18n@4.0.1(svelte@5.55.2): + svelte-i18n@4.0.1(svelte@5.55.8(@typescript-eslint/types@8.59.4)): dependencies: cli-color: 2.0.4 deepmerge: 4.3.1 @@ -25123,26 +25503,26 @@ snapshots: estree-walker: 2.0.2 intl-messageformat: 10.7.18 sade: 1.8.1 - svelte: 5.55.2 + svelte: 5.55.8(@typescript-eslint/types@8.59.4) tiny-glob: 0.2.9 - svelte-jsoneditor@3.12.0(svelte@5.55.2): + svelte-jsoneditor@3.12.0(svelte@5.55.8(@typescript-eslint/types@8.59.4)): dependencies: - '@codemirror/autocomplete': 6.20.1 + '@codemirror/autocomplete': 6.20.2 '@codemirror/commands': 6.10.3 '@codemirror/lang-json': 6.0.2 '@codemirror/language': 6.12.3 - '@codemirror/lint': 6.9.5 + '@codemirror/lint': 6.9.6 '@codemirror/search': 6.7.0 '@codemirror/state': 6.6.0 - '@codemirror/view': 6.41.1 + '@codemirror/view': 6.43.0 '@fortawesome/free-regular-svg-icons': 7.2.0 '@fortawesome/free-solid-svg-icons': 7.2.0 '@jsonquerylang/jsonquery': 5.1.1 '@lezer/highlight': 1.2.3 - '@replit/codemirror-indentation-markers': 6.5.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.41.1) - ajv: 8.18.0 - codemirror-wrapped-line-indent: 1.0.9(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.41.1) + '@replit/codemirror-indentation-markers': 6.5.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.43.0) + ajv: 8.20.0 + codemirror-wrapped-line-indent: 1.0.9(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.43.0) diff-sequences: 29.6.3 immutable-json-patch: 6.0.2 jmespath: 0.16.0 @@ -25153,82 +25533,78 @@ snapshots: memoize-one: 6.0.0 natural-compare-lite: 1.4.0 sass: 1.99.0 - svelte: 5.55.2 - svelte-awesome: 3.3.5(svelte@5.55.2) + svelte: 5.55.8(@typescript-eslint/types@8.59.4) + svelte-awesome: 3.3.5(svelte@5.55.8(@typescript-eslint/types@8.59.4)) svelte-select: 5.8.3 vanilla-picker: 2.12.3 - svelte-maplibre@1.3.0(svelte@5.55.2): + svelte-maplibre@1.3.0(svelte@5.55.8(@typescript-eslint/types@8.59.4)): dependencies: d3-geo: 3.1.1 dequal: 2.0.3 just-compare: 2.3.0 maplibre-gl: 5.24.0 pmtiles: 3.2.1 - svelte: 5.55.2 + svelte: 5.55.8(@typescript-eslint/types@8.59.4) - svelte-parse-markup@0.1.5(svelte@5.55.2): + svelte-parse-markup@0.1.5(svelte@5.55.8(@typescript-eslint/types@8.59.4)): dependencies: - svelte: 5.55.2 + svelte: 5.55.8(@typescript-eslint/types@8.59.4) - svelte-persisted-store@0.12.0(svelte@5.55.2): + svelte-persisted-store@0.12.0(svelte@5.55.8(@typescript-eslint/types@8.59.4)): dependencies: - svelte: 5.55.2 + svelte: 5.55.8(@typescript-eslint/types@8.59.4) svelte-select@5.8.3: dependencies: svelte-floating-ui: 1.5.8 - svelte-toolbelt@0.10.6(@sveltejs/kit@2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2): + svelte-toolbelt@0.10.6(@sveltejs/kit@2.60.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.55.8(@typescript-eslint/types@8.59.4))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4))(typescript@6.0.3)(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4)): dependencies: clsx: 2.1.1 - runed: 0.35.1(@sveltejs/kit@2.57.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2)(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.2) + runed: 0.35.1(@sveltejs/kit@2.60.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.55.8(@typescript-eslint/types@8.59.4))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4))(typescript@6.0.3)(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.55.8(@typescript-eslint/types@8.59.4)) style-to-object: 1.0.14 - svelte: 5.55.2 + svelte: 5.55.8(@typescript-eslint/types@8.59.4) transitivePeerDependencies: - '@sveltejs/kit' - svelte@5.55.2: + svelte@5.55.8(@typescript-eslint/types@8.59.4): dependencies: '@jridgewell/remapping': 2.3.5 '@jridgewell/sourcemap-codec': 1.5.5 - '@sveltejs/acorn-typescript': 1.0.9(acorn@8.16.0) - '@types/estree': 1.0.8 + '@sveltejs/acorn-typescript': 1.0.10(acorn@8.16.0) + '@types/estree': 1.0.9 '@types/trusted-types': 2.0.7 acorn: 8.16.0 aria-query: 5.3.1 axobject-query: 4.1.0 clsx: 2.1.1 - devalue: 5.7.1 + devalue: 5.8.1 esm-env: 1.2.2 - esrap: 2.2.4 + esrap: 2.2.9(@typescript-eslint/types@8.59.4) is-reference: 3.0.3 locate-character: 3.0.0 magic-string: 0.30.21 zimmerframe: 1.1.4 + transitivePeerDependencies: + - '@typescript-eslint/types' svg-parser@2.0.4: {} - svgo@3.3.2: + svgo@3.3.3: dependencies: - '@trysound/sax': 0.2.0 commander: 7.2.0 css-select: 5.2.2 css-tree: 2.3.1 css-what: 6.2.2 csso: 5.0.5 picocolors: 1.1.1 + sax: 1.6.0 - swagger-ui-dist@5.32.4: + swagger-ui-dist@5.32.6: dependencies: '@scarf/scarf': 1.4.0 - swr@2.3.8(react@19.2.5): - dependencies: - dequal: 2.0.3 - react: 19.2.5 - use-sync-external-store: 1.6.0(react@19.2.5) - symbol-observable@4.0.0: {} symbol-tree@3.2.4: @@ -25238,7 +25614,7 @@ snapshots: dependencies: '@pkgr/core': 0.2.9 - systeminformation@5.23.8: {} + systeminformation@5.31.6: {} tabbable@6.4.0: {} @@ -25246,29 +25622,29 @@ snapshots: tailwind-csstree@0.3.1: {} - tailwind-merge@3.5.0: {} + tailwind-merge@3.6.0: {} - tailwind-variants@3.2.2(tailwind-merge@3.5.0)(tailwindcss@4.2.4): + tailwind-variants@3.2.2(tailwind-merge@3.6.0)(tailwindcss@4.3.0): dependencies: - tailwindcss: 4.2.4 + tailwindcss: 4.3.0 optionalDependencies: - tailwind-merge: 3.5.0 + tailwind-merge: 3.6.0 - tailwindcss-email-variants@3.0.5(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.3)): + tailwindcss-email-variants@3.0.5(tailwindcss@3.4.19(tsx@4.22.3)(yaml@2.9.0)): dependencies: - tailwindcss: 3.4.19(tsx@4.21.0)(yaml@2.8.3) + tailwindcss: 3.4.19(tsx@4.22.3)(yaml@2.9.0) - tailwindcss-mso@2.0.3(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.3)): + tailwindcss-mso@2.0.3(tailwindcss@3.4.19(tsx@4.22.3)(yaml@2.9.0)): dependencies: - tailwindcss: 3.4.19(tsx@4.21.0)(yaml@2.8.3) + tailwindcss: 3.4.19(tsx@4.22.3)(yaml@2.9.0) - tailwindcss-preset-email@1.4.1(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.3)): + tailwindcss-preset-email@1.4.1(tailwindcss@3.4.19(tsx@4.22.3)(yaml@2.9.0)): dependencies: - tailwindcss: 3.4.19(tsx@4.21.0)(yaml@2.8.3) - tailwindcss-email-variants: 3.0.5(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.3)) - tailwindcss-mso: 2.0.3(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.3)) + tailwindcss: 3.4.19(tsx@4.22.3)(yaml@2.9.0) + tailwindcss-email-variants: 3.0.5(tailwindcss@3.4.19(tsx@4.22.3)(yaml@2.9.0)) + tailwindcss-mso: 2.0.3(tailwindcss@3.4.19(tsx@4.22.3)(yaml@2.9.0)) - tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.3): + tailwindcss@3.4.19(tsx@4.22.3)(yaml@2.9.0): dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 @@ -25284,19 +25660,19 @@ snapshots: normalize-path: 3.0.0 object-hash: 3.0.0 picocolors: 1.1.1 - postcss: 8.5.12 - postcss-import: 15.1.0(postcss@8.5.12) - postcss-js: 4.1.0(postcss@8.5.12) - postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.12)(tsx@4.21.0)(yaml@2.8.3) - postcss-nested: 6.2.0(postcss@8.5.12) + postcss: 8.5.15 + postcss-import: 15.1.0(postcss@8.5.15) + postcss-js: 4.1.0(postcss@8.5.15) + postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.15)(tsx@4.22.3)(yaml@2.9.0) + postcss-nested: 6.2.0(postcss@8.5.15) postcss-selector-parser: 6.1.2 - resolve: 1.22.11 + resolve: 1.22.12 sucrase: 3.35.1 transitivePeerDependencies: - tsx - yaml - tailwindcss@4.2.4: {} + tailwindcss@4.3.0: {} tapable@2.3.3: {} @@ -25310,7 +25686,7 @@ snapshots: tar-fs@3.1.2: dependencies: pump: 3.0.4 - tar-stream: 3.1.8 + tar-stream: 3.2.0 optionalDependencies: bare-fs: 4.7.1 bare-path: 3.0.0 @@ -25327,9 +25703,9 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 - tar-stream@3.1.8: + tar-stream@3.2.0: dependencies: - b4a: 1.8.0 + b4a: 1.8.1 bare-fs: 4.7.1 fast-fifo: 1.3.2 streamx: 2.25.0 @@ -25347,7 +25723,7 @@ snapshots: mkdirp: 1.0.4 yallist: 4.0.0 - tar@7.5.7: + tar@7.5.15: dependencies: '@isaacs/fs-minipass': 4.0.1 chownr: 3.0.0 @@ -25362,39 +25738,49 @@ snapshots: - bare-abort-controller - react-native-b4a - terser-webpack-plugin@5.4.0(@swc/core@1.15.30(@swc/helpers@0.5.21))(esbuild@0.28.0)(webpack@5.106.0(@swc/core@1.15.30(@swc/helpers@0.5.21))(esbuild@0.28.0)): + terser-webpack-plugin@5.6.0(@swc/core@1.15.33(@swc/helpers@0.5.23))(esbuild@0.28.0)(lightningcss@1.32.0)(webpack@5.106.0(@swc/core@1.15.33(@swc/helpers@0.5.23))(esbuild@0.28.0)(lightningcss@1.32.0)): dependencies: '@jridgewell/trace-mapping': 0.3.31 jest-worker: 27.5.1 schema-utils: 4.3.3 - terser: 5.46.1 - webpack: 5.106.0(@swc/core@1.15.30(@swc/helpers@0.5.21))(esbuild@0.28.0) + terser: 5.47.1 + webpack: 5.106.0(@swc/core@1.15.33(@swc/helpers@0.5.23))(esbuild@0.28.0)(lightningcss@1.32.0) optionalDependencies: - '@swc/core': 1.15.30(@swc/helpers@0.5.21) + '@swc/core': 1.15.33(@swc/helpers@0.5.23) esbuild: 0.28.0 + lightningcss: 1.32.0 - terser-webpack-plugin@5.4.0(webpack@5.106.2): + terser-webpack-plugin@5.6.0(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(html-minifier-terser@7.2.0)(postcss@8.5.15)(webpack@5.107.0(postcss@8.5.15)): dependencies: '@jridgewell/trace-mapping': 0.3.31 jest-worker: 27.5.1 schema-utils: 4.3.3 - terser: 5.46.1 - webpack: 5.106.2 + terser: 5.47.1 + webpack: 5.107.0(postcss@8.5.15) + optionalDependencies: + clean-css: 5.3.3 + cssnano: 6.1.2(postcss@8.5.15) + html-minifier-terser: 7.2.0 + postcss: 8.5.15 - terser@5.46.1: + terser-webpack-plugin@5.6.0(postcss@8.5.15)(webpack@5.107.0(postcss@8.5.15)): + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + jest-worker: 27.5.1 + schema-utils: 4.3.3 + terser: 5.47.1 + webpack: 5.107.0(postcss@8.5.15) + optionalDependencies: + postcss: 8.5.15 + + terser@5.47.1: dependencies: '@jridgewell/source-map': 0.3.11 acorn: 8.16.0 commander: 2.20.3 source-map-support: 0.5.21 - test-exclude@7.0.2: - dependencies: - '@istanbuljs/schema': 0.1.6 - glob: 10.5.0 - minimatch: 10.2.5 - - testcontainers@11.14.0: + testcontainers@12.0.1: dependencies: '@balena/dockerignore': 1.0.2 '@types/dockerode': 4.0.1 @@ -25403,13 +25789,13 @@ snapshots: byline: 5.0.0 debug: 4.4.3 docker-compose: 1.4.2 - dockerode: 4.0.10 + dockerode: 5.0.0 get-port: 7.2.0 proper-lockfile: 4.1.2 properties-reader: 3.0.1 ssh-remote-port-forward: 1.0.4 tar-fs: 3.1.2 - tmp: 0.2.5 + tmp: 0.2.7 undici: 7.25.0 transitivePeerDependencies: - bare-abort-controller @@ -25419,7 +25805,7 @@ snapshots: text-decoder@1.2.7: dependencies: - b4a: 1.8.0 + b4a: 1.8.1 transitivePeerDependencies: - react-native-b4a @@ -25433,15 +25819,13 @@ snapshots: dependencies: any-promise: 1.3.0 - thingies@2.5.0(tslib@2.8.1): + thingies@2.6.0(tslib@2.8.1): dependencies: tslib: 2.8.1 three@0.179.1: {} - three@0.182.0: {} - - throttleit@2.1.0: {} + three@0.184.0: {} through@2.3.8: {} @@ -25467,7 +25851,7 @@ snapshots: tinyexec@0.3.2: {} - tinyexec@1.1.1: {} + tinyexec@1.1.2: {} tinyglobby@0.2.16: dependencies: @@ -25492,7 +25876,7 @@ snapshots: tldts-core: 6.1.86 optional: true - tmp@0.2.5: {} + tmp@0.2.7: {} to-regex-range@5.0.1: dependencies: @@ -25549,6 +25933,16 @@ snapshots: ts-interface-checker@0.1.13: {} + tsc-alias@1.8.17: + dependencies: + chokidar: 3.6.0 + commander: 9.5.0 + get-tsconfig: 4.14.0 + globby: 11.1.0 + mylas: 2.1.14 + normalize-path: 3.0.0 + plimit-lit: 1.6.1 + tsconfck@3.1.6(typescript@6.0.3): optionalDependencies: typescript: 6.0.3 @@ -25556,7 +25950,7 @@ snapshots: tsconfig-paths-webpack-plugin@4.2.0: dependencies: chalk: 4.1.2 - enhanced-resolve: 5.21.0 + enhanced-resolve: 5.21.5 tapable: 2.3.3 tsconfig-paths: 4.2.0 @@ -25566,17 +25960,27 @@ snapshots: minimist: 1.2.8 strip-bom: 3.0.0 + tslib@1.14.1: {} + tslib@2.8.1: {} tsscmp@1.0.6: {} - tsx@4.21.0: + tsx@4.22.3: dependencies: - esbuild: 0.27.4 - get-tsconfig: 4.13.0 + esbuild: 0.28.0 optionalDependencies: fsevents: 2.3.3 + tsyringe@4.10.0: + dependencies: + tslib: 1.14.1 + + tunnel-agent@0.6.0: + dependencies: + safe-buffer: 5.2.1 + optional: true + tweetnacl@0.14.5: {} type-check@0.4.0: @@ -25598,9 +26002,9 @@ snapshots: media-typer: 0.3.0 mime-types: 2.1.35 - type-is@2.0.1: + type-is@2.1.0: dependencies: - content-type: 1.0.5 + content-type: 2.0.0 media-typer: 1.1.0 mime-types: 3.0.2 @@ -25612,13 +26016,13 @@ snapshots: typedarray@0.0.6: {} - typescript-eslint@8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3): + typescript-eslint@8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.59.0(@typescript-eslint/parser@8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3))(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) - '@typescript-eslint/parser': 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) - '@typescript-eslint/typescript-estree': 8.59.0(typescript@6.0.3) - '@typescript-eslint/utils': 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) - eslint: 10.2.1(jiti@2.6.1) + '@typescript-eslint/eslint-plugin': 8.59.4(@typescript-eslint/parser@8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/parser': 8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/typescript-estree': 8.59.4(typescript@6.0.3) + '@typescript-eslint/utils': 8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) + eslint: 10.4.0(jiti@2.7.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -25635,8 +26039,6 @@ snapshots: is-standalone-pwa: 0.1.1 ua-is-frozen: 0.1.2 - ufo@1.6.2: {} - uglify-js@3.19.3: optional: true @@ -25652,8 +26054,7 @@ snapshots: undici-types@7.16.0: {} - undici-types@7.19.2: - optional: true + undici@6.25.0: {} undici@7.25.0: {} @@ -25690,14 +26091,6 @@ snapshots: trough: 1.0.5 vfile: 4.2.1 - unique-filename@5.0.0: - dependencies: - unique-slug: 6.0.0 - - unique-slug@6.0.0: - dependencies: - imurmurhash: 0.1.4 - unique-string@3.0.0: dependencies: crypto-random-string: 4.0.0 @@ -25744,7 +26137,7 @@ snapshots: unist-util-is: 4.1.0 unist-util-visit-parents: 3.1.1 - unist-util-visit@5.0.0: + unist-util-visit@5.1.0: dependencies: '@types/unist': 3.0.3 unist-util-is: 6.0.1 @@ -25754,10 +26147,10 @@ snapshots: unpipe@1.0.0: {} - unplugin-swc@1.5.9(@swc/core@1.15.30(@swc/helpers@0.5.21))(rollup@4.55.1): + unplugin-swc@1.5.9(@swc/core@1.15.33(@swc/helpers@0.5.23))(rollup@4.60.4): dependencies: - '@rollup/pluginutils': 5.3.0(rollup@4.55.1) - '@swc/core': 1.15.30(@swc/helpers@0.5.21) + '@rollup/pluginutils': 5.3.0(rollup@4.60.4) + '@swc/core': 1.15.33(@swc/helpers@0.5.23) load-tsconfig: 0.2.5 unplugin: 2.3.11 transitivePeerDependencies: @@ -25789,7 +26182,7 @@ snapshots: is-yarn-global: 0.4.1 latest-version: 7.0.0 pupa: 3.3.0 - semver: 7.7.4 + semver: 7.8.1 semver-diff: 4.0.0 xdg-basedir: 5.1.0 @@ -25799,26 +26192,22 @@ snapshots: dependencies: punycode: 2.3.1 - url-loader@4.1.1(file-loader@6.2.0(webpack@5.106.2))(webpack@5.106.2): + url-loader@4.1.1(file-loader@6.2.0(webpack@5.107.0(postcss@8.5.15)))(webpack@5.107.0(postcss@8.5.15)): dependencies: loader-utils: 2.0.4 mime-types: 2.1.35 schema-utils: 3.3.0 - webpack: 5.106.2 + webpack: 5.107.0(postcss@8.5.15) optionalDependencies: - file-loader: 6.2.0(webpack@5.106.2) + file-loader: 6.2.0(webpack@5.107.0(postcss@8.5.15)) url@0.11.4: dependencies: punycode: 1.4.1 - qs: 6.14.1 + qs: 6.15.2 urlpattern-polyfill@8.0.2: {} - use-sync-external-store@1.6.0(react@19.2.5): - dependencies: - react: 19.2.5 - utf8-byte-length@1.0.5: {} util-deprecate@1.0.2: {} @@ -25829,23 +26218,19 @@ snapshots: utils-merge@1.0.1: {} - utimes@5.2.1(encoding@0.1.13): + utimes@5.2.1: dependencies: - '@mapbox/node-pre-gyp': 1.0.11(encoding@0.1.13) + '@mapbox/node-pre-gyp': 1.0.11 node-addon-api: 4.3.0 transitivePeerDependencies: - encoding - supports-color - uuid@10.0.0: {} - - uuid@11.1.0: {} - uuid@14.0.0: {} uuid@8.3.2: {} - valibot@1.3.1(typescript@6.0.3): + valibot@1.4.0(typescript@6.0.3): optionalDependencies: typescript: 6.0.3 @@ -25888,22 +26273,21 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vite-imagetools@9.0.3(rollup@4.55.1): + vite-imagetools@9.0.3(rollup@4.60.4): dependencies: - '@rollup/pluginutils': 5.3.0(rollup@4.55.1) + '@rollup/pluginutils': 5.3.0(rollup@4.60.4) imagetools-core: 9.1.0 sharp: 0.34.5 transitivePeerDependencies: - rollup - - supports-color - vite-node@3.2.4(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3): + vite-node@3.2.4(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0): dependencies: cac: 6.7.14 debug: 4.4.3 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 7.3.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vite: 7.3.3(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0) transitivePeerDependencies: - '@types/node' - jiti @@ -25918,81 +26302,64 @@ snapshots: - tsx - yaml - vite-tsconfig-paths@6.1.1(typescript@6.0.3)(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): + vite-tsconfig-paths@6.1.1(typescript@6.0.3)(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)): dependencies: debug: 4.4.3 globrex: 0.1.2 tsconfck: 3.1.6(typescript@6.0.3) - vite: 8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0) transitivePeerDependencies: - supports-color - typescript - vite@7.3.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3): + vite@7.3.3(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0): dependencies: - esbuild: 0.27.4 + esbuild: 0.27.7 fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 - postcss: 8.5.12 - rollup: 4.55.1 + postcss: 8.5.15 + rollup: 4.60.4 tinyglobby: 0.2.16 optionalDependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 fsevents: 2.3.3 - jiti: 2.6.1 + jiti: 2.7.0 lightningcss: 1.32.0 sass: 1.99.0 - terser: 5.46.1 - tsx: 4.21.0 - yaml: 2.8.3 + terser: 5.47.1 + tsx: 4.22.3 + yaml: 2.9.0 - vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3): + vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0): dependencies: lightningcss: 1.32.0 picomatch: 4.0.4 - postcss: 8.5.12 - rolldown: 1.0.0-rc.17 + postcss: 8.5.15 + rolldown: 1.0.1 tinyglobby: 0.2.16 optionalDependencies: - '@types/node': 24.12.2 + '@types/node': 24.12.4 esbuild: 0.28.0 fsevents: 2.3.3 - jiti: 2.6.1 + jiti: 2.7.0 sass: 1.99.0 - terser: 5.46.1 - tsx: 4.21.0 - yaml: 2.8.3 + terser: 5.47.1 + tsx: 4.22.3 + yaml: 2.9.0 - vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3): - dependencies: - lightningcss: 1.32.0 - picomatch: 4.0.4 - postcss: 8.5.12 - rolldown: 1.0.0-rc.17 - tinyglobby: 0.2.16 + vitefu@1.1.3(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)): optionalDependencies: - '@types/node': 25.6.0 - esbuild: 0.28.0 - fsevents: 2.3.3 - jiti: 2.6.1 - sass: 1.99.0 - terser: 5.46.1 - tsx: 4.21.0 - yaml: 2.8.3 + vite: 8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0) - vitefu@1.1.2(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): - optionalDependencies: - vite: 8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) - - vitest-fetch-mock@0.4.5(vitest@4.1.5): + vitest-fetch-mock@0.4.5(vitest@4.1.7): dependencies: - vitest: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@24.12.2)(@vitest/coverage-v8@4.1.5)(happy-dom@20.9.0)(jsdom@26.1.0(canvas@2.11.2))(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + vitest: 4.1.7(@opentelemetry/api@1.9.1)(@types/node@24.12.4)(@vitest/coverage-v8@4.1.7)(happy-dom@20.9.0)(jsdom@26.1.0(canvas@3.2.3))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)) - vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.12.2)(happy-dom@20.9.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.32.0)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3): + vitest@3.2.4(@types/debug@4.1.13)(@types/node@24.12.4)(happy-dom@20.9.0)(jiti@2.7.0)(jsdom@26.1.0(canvas@3.2.3))(lightningcss@1.32.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0): dependencies: '@types/chai': 5.2.3 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@7.3.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + '@vitest/mocker': 3.2.4(vite@7.3.3(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 @@ -26010,14 +26377,14 @@ snapshots: tinyglobby: 0.2.16 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 7.3.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) - vite-node: 3.2.4(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vite: 7.3.3(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0) + vite-node: 3.2.4(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: - '@types/debug': 4.1.12 - '@types/node': 24.12.2 + '@types/debug': 4.1.13 + '@types/node': 24.12.4 happy-dom: 20.9.0 - jsdom: 26.1.0(canvas@2.11.2) + jsdom: 26.1.0(canvas@3.2.3) transitivePeerDependencies: - jiti - less @@ -26032,15 +26399,15 @@ snapshots: - tsx - yaml - vitest@4.1.5(@opentelemetry/api@1.9.1)(@types/node@24.12.2)(@vitest/coverage-v8@4.1.5)(happy-dom@20.9.0)(jsdom@26.1.0(canvas@2.11.2(encoding@0.1.13)))(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): + vitest@4.1.7(@opentelemetry/api@1.9.1)(@types/node@24.12.4)(@vitest/coverage-v8@4.1.7)(happy-dom@20.9.0)(jsdom@26.1.0(canvas@3.2.3))(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)): dependencies: - '@vitest/expect': 4.1.5 - '@vitest/mocker': 4.1.5(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) - '@vitest/pretty-format': 4.1.5 - '@vitest/runner': 4.1.5 - '@vitest/snapshot': 4.1.5 - '@vitest/spy': 4.1.5 - '@vitest/utils': 4.1.5 + '@vitest/expect': 4.1.7 + '@vitest/mocker': 4.1.7(vite@8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0)) + '@vitest/pretty-format': 4.1.7 + '@vitest/runner': 4.1.7 + '@vitest/snapshot': 4.1.7 + '@vitest/spy': 4.1.7 + '@vitest/utils': 4.1.7 es-module-lexer: 2.1.0 expect-type: 1.3.0 magic-string: 0.30.21 @@ -26049,99 +26416,20 @@ snapshots: picomatch: 4.0.4 std-env: 4.1.0 tinybench: 2.9.0 - tinyexec: 1.1.1 + tinyexec: 1.1.2 tinyglobby: 0.2.16 tinyrainbow: 3.1.0 - vite: 8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.13(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.99.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: '@opentelemetry/api': 1.9.1 - '@types/node': 24.12.2 - '@vitest/coverage-v8': 4.1.5(vitest@4.1.5) + '@types/node': 24.12.4 + '@vitest/coverage-v8': 4.1.7(vitest@4.1.7) happy-dom: 20.9.0 - jsdom: 26.1.0(canvas@2.11.2(encoding@0.1.13)) + jsdom: 26.1.0(canvas@3.2.3) transitivePeerDependencies: - msw - vitest@4.1.5(@opentelemetry/api@1.9.1)(@types/node@24.12.2)(@vitest/coverage-v8@4.1.5)(happy-dom@20.9.0)(jsdom@26.1.0(canvas@2.11.2))(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): - dependencies: - '@vitest/expect': 4.1.5 - '@vitest/mocker': 4.1.5(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) - '@vitest/pretty-format': 4.1.5 - '@vitest/runner': 4.1.5 - '@vitest/snapshot': 4.1.5 - '@vitest/spy': 4.1.5 - '@vitest/utils': 4.1.5 - es-module-lexer: 2.1.0 - expect-type: 1.3.0 - magic-string: 0.30.21 - obug: 2.1.1 - pathe: 2.0.3 - picomatch: 4.0.4 - std-env: 4.1.0 - tinybench: 2.9.0 - tinyexec: 1.1.1 - tinyglobby: 0.2.16 - tinyrainbow: 3.1.0 - vite: 8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) - why-is-node-running: 2.3.0 - optionalDependencies: - '@opentelemetry/api': 1.9.1 - '@types/node': 24.12.2 - '@vitest/coverage-v8': 4.1.5(vitest@4.1.5) - happy-dom: 20.9.0 - jsdom: 26.1.0(canvas@2.11.2) - transitivePeerDependencies: - - msw - - vitest@4.1.5(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(@vitest/coverage-v8@4.1.5)(happy-dom@20.9.0)(jsdom@26.1.0(canvas@2.11.2))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): - dependencies: - '@vitest/expect': 4.1.5 - '@vitest/mocker': 4.1.5(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) - '@vitest/pretty-format': 4.1.5 - '@vitest/runner': 4.1.5 - '@vitest/snapshot': 4.1.5 - '@vitest/spy': 4.1.5 - '@vitest/utils': 4.1.5 - es-module-lexer: 2.1.0 - expect-type: 1.3.0 - magic-string: 0.30.21 - obug: 2.1.1 - pathe: 2.0.3 - picomatch: 4.0.4 - std-env: 4.1.0 - tinybench: 2.9.0 - tinyexec: 1.1.1 - tinyglobby: 0.2.16 - tinyrainbow: 3.1.0 - vite: 8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) - why-is-node-running: 2.3.0 - optionalDependencies: - '@opentelemetry/api': 1.9.1 - '@types/node': 25.6.0 - '@vitest/coverage-v8': 4.1.5(vitest@4.1.5) - happy-dom: 20.9.0 - jsdom: 26.1.0(canvas@2.11.2) - transitivePeerDependencies: - - msw - - vscode-jsonrpc@8.2.0: {} - - vscode-languageserver-protocol@3.17.5: - dependencies: - vscode-jsonrpc: 8.2.0 - vscode-languageserver-types: 3.17.5 - - vscode-languageserver-textdocument@1.0.12: {} - - vscode-languageserver-types@3.17.5: {} - - vscode-languageserver@9.0.1: - dependencies: - vscode-languageserver-protocol: 3.17.5 - - vscode-uri@3.0.8: {} - w3c-keyname@2.2.8: {} w3c-xmlserializer@5.0.0: @@ -26175,7 +26463,7 @@ snapshots: dependencies: '@discoveryjs/json-ext': 0.5.7 acorn: 8.16.0 - acorn-walk: 8.3.4 + acorn-walk: 8.3.5 commander: 7.2.0 debounce: 1.2.1 escape-string-regexp: 4.0.0 @@ -26189,7 +26477,7 @@ snapshots: - bufferutil - utf-8-validate - webpack-dev-middleware@7.4.5(tslib@2.8.1)(webpack@5.106.2): + webpack-dev-middleware@7.4.5(tslib@2.8.1)(webpack@5.107.0(postcss@8.5.15)): dependencies: colorette: 2.0.20 memfs: 4.57.2(tslib@2.8.1) @@ -26198,16 +26486,16 @@ snapshots: range-parser: 1.2.1 schema-utils: 4.3.3 optionalDependencies: - webpack: 5.106.2 + webpack: 5.107.0(postcss@8.5.15) transitivePeerDependencies: - tslib - webpack-dev-server@5.2.2(tslib@2.8.1)(webpack@5.106.2): + webpack-dev-server@5.2.4(tslib@2.8.1)(webpack@5.107.0(postcss@8.5.15)): dependencies: '@types/bonjour': 3.5.13 '@types/connect-history-api-fallback': 1.5.4 '@types/express': 4.17.25 - '@types/express-serve-static-core': 4.19.7 + '@types/express-serve-static-core': 4.19.8 '@types/serve-index': 1.9.4 '@types/serve-static': 1.15.10 '@types/sockjs': 0.3.36 @@ -26218,22 +26506,22 @@ snapshots: colorette: 2.0.20 compression: 1.8.1 connect-history-api-fallback: 2.0.0 - express: 4.22.1 + express: 4.22.2 graceful-fs: 4.2.11 http-proxy-middleware: 2.0.9(@types/express@4.17.25) - ipaddr.js: 2.3.0 - launch-editor: 2.12.0 + ipaddr.js: 2.4.0 + launch-editor: 2.13.2 open: 10.2.0 p-retry: 6.2.1 schema-utils: 4.3.3 - selfsigned: 2.4.1 - serve-index: 1.9.1 + selfsigned: 5.5.0 + serve-index: 1.9.2 sockjs: 0.3.24 spdy: 4.0.2 - webpack-dev-middleware: 7.4.5(tslib@2.8.1)(webpack@5.106.2) - ws: 8.20.0 + webpack-dev-middleware: 7.4.5(tslib@2.8.1)(webpack@5.107.0(postcss@8.5.15)) + ws: 8.20.1 optionalDependencies: - webpack: 5.106.2 + webpack: 5.107.0(postcss@8.5.15) transitivePeerDependencies: - bufferutil - debug @@ -26255,14 +26543,14 @@ snapshots: webpack-node-externals@3.0.0: {} - webpack-sources@3.3.4: {} + webpack-sources@3.4.1: {} webpack-virtual-modules@0.6.2: {} - webpack@5.106.0(@swc/core@1.15.30(@swc/helpers@0.5.21))(esbuild@0.28.0): + webpack@5.106.0(@swc/core@1.15.33(@swc/helpers@0.5.23))(esbuild@0.28.0)(lightningcss@1.32.0): dependencies: '@types/eslint-scope': 3.7.7 - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 '@types/json-schema': 7.0.15 '@webassemblyjs/ast': 1.14.1 '@webassemblyjs/wasm-edit': 1.14.1 @@ -26271,30 +26559,38 @@ snapshots: acorn-import-phases: 1.0.4(acorn@8.16.0) browserslist: 4.28.2 chrome-trace-event: 1.0.4 - enhanced-resolve: 5.21.0 + enhanced-resolve: 5.21.5 es-module-lexer: 2.1.0 eslint-scope: 5.1.1 events: 3.3.0 glob-to-regexp: 0.4.1 graceful-fs: 4.2.11 json-parse-even-better-errors: 2.3.1 - loader-runner: 4.3.1 + loader-runner: 4.3.2 mime-types: 2.1.35 neo-async: 2.6.2 schema-utils: 4.3.3 tapable: 2.3.3 - terser-webpack-plugin: 5.4.0(@swc/core@1.15.30(@swc/helpers@0.5.21))(esbuild@0.28.0)(webpack@5.106.0(@swc/core@1.15.30(@swc/helpers@0.5.21))(esbuild@0.28.0)) + terser-webpack-plugin: 5.6.0(@swc/core@1.15.33(@swc/helpers@0.5.23))(esbuild@0.28.0)(lightningcss@1.32.0)(webpack@5.106.0(@swc/core@1.15.33(@swc/helpers@0.5.23))(esbuild@0.28.0)(lightningcss@1.32.0)) watchpack: 2.5.1 - webpack-sources: 3.3.4 + webpack-sources: 3.4.1 transitivePeerDependencies: + - '@minify-html/node' - '@swc/core' + - '@swc/css' + - '@swc/html' + - clean-css + - cssnano + - csso - esbuild + - html-minifier-terser + - lightningcss + - postcss - uglify-js - webpack@5.106.2: + webpack@5.107.0(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(html-minifier-terser@7.2.0)(postcss@8.5.15): dependencies: - '@types/eslint-scope': 3.7.7 - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 '@types/json-schema': 7.0.15 '@webassemblyjs/ast': 1.14.1 '@webassemblyjs/wasm-edit': 1.14.1 @@ -26303,33 +26599,81 @@ snapshots: acorn-import-phases: 1.0.4(acorn@8.16.0) browserslist: 4.28.2 chrome-trace-event: 1.0.4 - enhanced-resolve: 5.21.0 + enhanced-resolve: 5.21.5 es-module-lexer: 2.1.0 eslint-scope: 5.1.1 events: 3.3.0 glob-to-regexp: 0.4.1 graceful-fs: 4.2.11 - loader-runner: 4.3.1 + loader-runner: 4.3.2 mime-db: 1.54.0 neo-async: 2.6.2 schema-utils: 4.3.3 tapable: 2.3.3 - terser-webpack-plugin: 5.4.0(webpack@5.106.2) + terser-webpack-plugin: 5.6.0(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(html-minifier-terser@7.2.0)(postcss@8.5.15)(webpack@5.107.0(postcss@8.5.15)) watchpack: 2.5.1 - webpack-sources: 3.3.4 + webpack-sources: 3.4.1 transitivePeerDependencies: + - '@minify-html/node' - '@swc/core' + - '@swc/css' + - '@swc/html' + - clean-css + - cssnano + - csso - esbuild + - html-minifier-terser + - lightningcss + - postcss - uglify-js - webpackbar@7.0.0(webpack@5.106.2): + webpack@5.107.0(postcss@8.5.15): + dependencies: + '@types/estree': 1.0.9 + '@types/json-schema': 7.0.15 + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/wasm-edit': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + acorn: 8.16.0 + acorn-import-phases: 1.0.4(acorn@8.16.0) + browserslist: 4.28.2 + chrome-trace-event: 1.0.4 + enhanced-resolve: 5.21.5 + es-module-lexer: 2.1.0 + eslint-scope: 5.1.1 + events: 3.3.0 + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + loader-runner: 4.3.2 + mime-db: 1.54.0 + neo-async: 2.6.2 + schema-utils: 4.3.3 + tapable: 2.3.3 + terser-webpack-plugin: 5.6.0(postcss@8.5.15)(webpack@5.107.0(postcss@8.5.15)) + watchpack: 2.5.1 + webpack-sources: 3.4.1 + transitivePeerDependencies: + - '@minify-html/node' + - '@swc/core' + - '@swc/css' + - '@swc/html' + - clean-css + - cssnano + - csso + - esbuild + - html-minifier-terser + - lightningcss + - postcss + - uglify-js + + webpackbar@7.0.0(webpack@5.107.0(postcss@8.5.15)): dependencies: ansis: 3.17.0 consola: 3.4.2 pretty-time: 1.1.0 std-env: 3.10.0 optionalDependencies: - webpack: 5.106.2 + webpack: 5.107.0(postcss@8.5.15) websocket-driver@0.7.4: dependencies: @@ -26430,9 +26774,7 @@ snapshots: ws@7.5.10: {} - ws@8.18.3: {} - - ws@8.20.0: {} + ws@8.20.1: {} wsl-utils@0.1.0: dependencies: @@ -26447,7 +26789,7 @@ snapshots: xml-js@1.6.11: dependencies: - sax: 1.4.3 + sax: 1.6.0 xml-name-validator@5.0.0: optional: true @@ -26471,7 +26813,7 @@ snapshots: yaml@1.10.3: {} - yaml@2.8.3: {} + yaml@2.9.0: {} yargs-parser@18.1.3: dependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index d9ac5ddcee..a848deca82 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,14 +1,13 @@ packages: - - cli + - packages/** - docs - e2e - - e2e-auth-server - i18n - - open-api/typescript-sdk - server - plugins - web - .github + - packages/* ignoredBuiltDependencies: - '@nestjs/core' - '@parcel/watcher' @@ -30,7 +29,7 @@ onlyBuiltDependencies: - '@tailwindcss/oxide' - bcrypt overrides: - canvas: 2.11.2 + canvas: 3.2.3 sharp: ^0.34.5 # pending docusaurus 3.10.1 webpackbar: ^7.0.0 @@ -61,6 +60,9 @@ packageExtensions: dependencies: node-addon-api: '*' node-gyp: '*' + '@nestjs/swagger': + peerDependencies: + typescript: '*' dedupePeerDependents: false preferWorkspacePackages: true injectWorkspacePackages: true diff --git a/readme_i18n/README_ar_JO.md b/readme_i18n/README_ar_JO.md index e0e13eeaf6..860ba24eaf 100644 --- a/readme_i18n/README_ar_JO.md +++ b/readme_i18n/README_ar_JO.md @@ -37,29 +37,24 @@ เธ เธฒเธฉเธฒเน„เธ—เธข

-## ุชู†ุตู„ +> [!WARNING] +> โš ๏ธ ุงุชุจุน ุฏุงุฆู…ู‹ุง ุฎุทุฉ ุงู„ู†ุณุฎ ุงู„ุงุญุชูŠุงุทูŠ [ูก-ูข-ูฃ](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) ู„ุตูˆุฑูƒ ูˆู…ู‚ุงุทุน ุงู„ููŠุฏูŠูˆ ุงู„ุซู…ูŠู†ุฉ ุงู„ุฎุงุตุฉ ุจูƒ +> -- โš ๏ธ ู‡ุฐุง ุงู„ุชุทุจูŠู‚ ู‚ูŠุฏ ุงู„ุชุทูˆูŠุฑ ุงู„ู†ุดุท ู„ู„ุบุงูŠุฉ -- โš ๏ธ ุชูˆู‚ุน ุงู„ุฃุฎุทุงุก ูˆุงู„ุชุบูŠูŠุฑุงุช ุงู„ุนุงุฌู„ุฉ -- โš ๏ธ **ู„ุง ุชุณุชุฎุฏู… ุงู„ุชุทุจูŠู‚ ุจุงุนุชุจุงุฑู‡ ุงู„ุทุฑูŠู‚ุฉ ุงู„ูˆุญูŠุฏุฉ ู„ุชุฎุฒูŠู† ุงู„ุตูˆุฑ ูˆู…ู‚ุงุทุน ุงู„ููŠุฏูŠูˆ ุงู„ุฎุงุตุฉ ุจูƒ** -- โš ๏ธ ุงุชุจุน ุฏุงุฆู…ู‹ุง ุฎุทุฉ ุงู„ู†ุณุฎ ุงู„ุงุญุชูŠุงุทูŠ [ูก-ูข-ูฃ](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) ู„ุตูˆุฑูƒ ูˆู…ู‚ุงุทุน ุงู„ููŠุฏูŠูˆ ุงู„ุซู…ูŠู†ุฉ ุงู„ุฎุงุตุฉ ุจูƒ +> [!NOTE] +> ูŠู…ูƒู†ูƒ ุงู„ุนุซูˆุฑ ุนู„ู‰ ุงู„ูˆุซุงุฆู‚ ุงู„ุฑุฆูŠุณูŠุฉุŒ ุจู…ุง ููŠ ุฐู„ูƒ ุฃุฏู„ุฉ ุงู„ุชุซุจูŠุชุŒ ุนู„ู‰ https://immich.app/ +## ุฑูˆุงุจุท -## ู…ุญุชูˆู‰ - -- [ุงู„ูˆุซุงุฆู‚ ุงู„ุฑุณู…ูŠุฉ](https://docs.immich.app) -- [ุฎุฑูŠุทุฉ ุงู„ุทุฑูŠู‚](https://github.com/orgs/immich-app/projects/1) -- [ุชุฌุฑูŠุจูŠ](#demo) -- [ุณู…ุงุช](#features) +- [ุงู„ูˆุซุงุฆู‚ ุงู„ุฑุณู…ูŠุฉ](https://docs.immich.app/) - [ู…ู‚ุฏู…ุฉ](https://docs.immich.app/overview/introduction) - [ุชุนู„ูŠู…ุงุช ุงู„ุชุญู…ูŠู„](https://docs.immich.app/install/requirements) +- [ุฎุฑูŠุทุฉ ุงู„ุทุฑูŠู‚](https://immich.app/roadmap) +- [ุชุฌุฑูŠุจูŠ](#ุชุฌุฑูŠุจูŠ) +- [ุณู…ุงุช](#ุณู…ุงุช) +- [ุงู„ุชุฑุฌู…ุงุช](https://docs.immich.app/developer/translations) - [ู‚ูˆุงุนุฏ ุงู„ู…ุณุงู‡ู…ุฉ](https://docs.immich.app/overview/support-the-project) -## ุชูˆุซูŠู‚ - -ูŠู…ูƒู†ูƒ ุงู„ุนุซูˆุฑ ุนู„ู‰ ุงู„ูˆุซุงุฆู‚ ุงู„ุฑุฆูŠุณูŠุฉุŒ ุจู…ุง ููŠ ุฐู„ูƒ ุฃุฏู„ุฉ ุงู„ุชุซุจูŠุชุŒ ู‡ู†ุง -https://immich.app - ## ุชุฌุฑูŠุจูŠ ูŠู…ูƒู†ูƒ ุงู„ูˆุตูˆู„ ุฅู„ู‰ ุงู„ุนุฑุถ ุงู„ุชูˆุถูŠุญูŠ ุนู„ู‰ ุงู„ูˆูŠุจ ุนู„ู‰ diff --git a/readme_i18n/README_ca_ES.md b/readme_i18n/README_ca_ES.md index d09362aa0f..49aebd9c10 100644 --- a/readme_i18n/README_ca_ES.md +++ b/readme_i18n/README_ca_ES.md @@ -37,26 +37,24 @@ เธ เธฒเธฉเธฒเน„เธ—เธข

-## Avรญs legal +> [!WARNING] +> โš ๏ธ Always follow [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) backup plan for your precious photos and videos! +> -- โš ๏ธ El projecte estร  en desenvolupament **molt actiu**. -- โš ๏ธ Espereu errors i canvis que poden trencar coses. -- โš ๏ธ **No utilitzeu l'aplicaciรณ com a รบnica manera de guardar les vostres fotos i vรญdeos!** +> [!NOTE] +> Podeu trobar la documentaciรณ principal, incloent les guies d'instalยทlaciรณ, a https://immich.app/. ## Contingut -- [Documentaciรณ oficial](https://docs.immich.app) -- [Mapa de ruta](https://github.com/orgs/immich-app/projects/1) -- [Demo](#demo) -- [Funcionalitats](#funcionalitats) +- [Documentaciรณ](https://docs.immich.app/) - [Introducciรณ](https://docs.immich.app/overview/introduction) - [Instalยทlaciรณ](https://docs.immich.app/install/requirements) +- [Mapa de ruta](https://immich.app/roadmap) +- [Demo](#demo) +- [Funcionalitats](#funcionalitats) +- [Traduccions](https://docs.immich.app/developer/translations) - [Directrius de contribuciรณ](https://docs.immich.app/overview/support-the-project) -## Documentaciรณ - -Podeu trobar la documentaciรณ principal, incloent les guies d'instalยทlaciรณ, a https://immich.app/. - ## Demo Podeu accedir a la demostraciรณ web a https://demo.immich.app. Per a l'aplicaciรณ mรฒbil, podeu utilitzar `https://demo.immich.app` com a "URL de punt final del servidor". diff --git a/readme_i18n/README_de_DE.md b/readme_i18n/README_de_DE.md index 488b05abcc..11f9e24686 100644 --- a/readme_i18n/README_de_DE.md +++ b/readme_i18n/README_de_DE.md @@ -38,7 +38,9 @@ เธ เธฒเธฉเธฒเน„เธ—เธข

-- โš ๏ธ Befolge immer die [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) Backup-Regel fรผr deine wertvollen Fotos und Videos! +> [!WARNING] +> โš ๏ธ Befolge immer die [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) Backup-Regel fรผr deine wertvollen Fotos und Videos! +> > [!NOTE] > Die Hauptdokumentation, einschlieรŸlich der Installationsanleitungen, befinden sich unter https://immich.app/. @@ -49,7 +51,7 @@ - [Offizielle Dokumentation](https://docs.immich.app) - [รœber Immich](https://docs.immich.app/overview/introduction) - [Installation](https://docs.immich.app/install/requirements) -- [Roadmap](https://github.com/orgs/immich-app/projects/1) +- [Roadmap](https://immich.app/roadmap) - [Demo](#demo) - [Funktionen](#funktionen) - [รœbersetzungen](https://docs.immich.app/developer/translations) diff --git a/readme_i18n/README_es_ES.md b/readme_i18n/README_es_ES.md index 032f8c50a8..2aa4d16ecf 100644 --- a/readme_i18n/README_es_ES.md +++ b/readme_i18n/README_es_ES.md @@ -37,27 +37,24 @@ เธ เธฒเธฉเธฒเน„เธ—เธข

-## Advertencia +> [!WARNING] +> โš ๏ธ Siempre sigue el plan de backups [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) para tus fotos y videos. +> -- โš ๏ธ El proyecto estรก en **activo desarrollo**. -- โš ๏ธ Es probable que haya errores y cambios disruptivos. -- โš ๏ธ **ยกNo utilices la aplicaciรณn como รบnica forma de almacenar tus fotos y videos!** -- โš ๏ธ Siempre sigue el plan de backups [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) para tus fotos y videos. +> [!NOTE] +> Puedes encontrar la documentaciรณn oficial, incluidas las guรญas de instalaciรณn, en . ## Contenido -- [Documentaciรณn oficial](https://docs.immich.app) -- [Hoja de ruta](https://github.com/orgs/immich-app/projects/1) -- [Demo](#demo) -- [Funciones](#funciones) +- [Documentaciรณn](https://docs.immich.app/) - [Introducciรณn](https://docs.immich.app/overview/introduction) - [Instalaciรณn](https://docs.immich.app/install/requirements) +- [Hoja de ruta](https://immich.app/roadmap) +- [Demo](#demo) +- [Funciones](#funciones) +- [Traducciones](https://docs.immich.app/developer/translations) - [Directrices para contribuir](https://docs.immich.app/overview/support-the-project) -## Documentaciรณn - -Puedes encontrar la documentaciรณn oficial, incluidas las guรญas de instalaciรณn, en . - ## Demo Puedes acceder a la demostraciรณn web en . Para la aplicaciรณn mรณvil, puedes usar `https://demo.immich.app` en la `URL del servidor`. diff --git a/readme_i18n/README_fr_FR.md b/readme_i18n/README_fr_FR.md index 349a0c49ce..e4fef66525 100644 --- a/readme_i18n/README_fr_FR.md +++ b/readme_i18n/README_fr_FR.md @@ -37,27 +37,24 @@ เธ เธฒเธฉเธฒเน„เธ—เธข

-## Clause de non-responsabilitรฉ +> [!WARNING] +> โš ๏ธ Ayez toujours un plan de sauvegarde en [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) pour vos prรฉcieuses photos et vidรฉos ! +> -- โš ๏ธ Le projet est en **trรจs fort** dรฉveloppement. -- โš ๏ธ Attendez-vous ร  rencontrer des bogues et des changements importants. -- โš ๏ธ **N'utilisez pas cette application comme seul support de sauvegarde de vos photos et vos vidรฉos.** -- โš ๏ธ Ayez toujours un plan de sauvegarde en [3-2-1](https://www.seagate.com/fr/fr/blog/what-is-a-3-2-1-backup-strategy/) pour vos prรฉcieuses photos et vidรฉos ! +> [!NOTE] +> Vous pouvez trouver la documentation principale ainsi que les guides d'installation sur https://immich.app/. ## Sommaire -- [Documentation officielle](https://docs.immich.app) -- [Feuille de route](https://github.com/orgs/immich-app/projects/1) -- [Dรฉmo](#dรฉmo) -- [Fonctionnalitรฉs](#fonctionnalitรฉs) +- [Documentation](https://docs.immich.app/) - [Introduction](https://docs.immich.app/overview/introduction) - [Installation](https://docs.immich.app/install/requirements) +- [Feuille de route](https://immich.app/roadmap) +- [Dรฉmo](#dรฉmo) +- [Fonctionnalitรฉs](#fonctionnalitรฉs) +- [Traductions](https://docs.immich.app/developer/translations) - [Contribution](https://docs.immich.app/overview/support-the-project) -## Documentation - -Vous pouvez trouver la documentation principale ainsi que les guides d'installation sur https://immich.app/. - ## Dรฉmo Vous pouvez accรฉder ร  la dรฉmo en ligne sur https://demo.immich.app. Pour l'application mobile, vous pouvez utiliser `https://demo.immich.app` dans le champ `URL du point d'accรจs au serveur` diff --git a/readme_i18n/README_it_IT.md b/readme_i18n/README_it_IT.md index 711840fd9d..5d8d8b0880 100644 --- a/readme_i18n/README_it_IT.md +++ b/readme_i18n/README_it_IT.md @@ -38,12 +38,9 @@ เธ เธฒเธฉเธฒเน„เธ—เธข

-## Avvertenze - -- โš ๏ธ Il progetto รจ in fase di sviluppo **molto attivo**. -- โš ๏ธ Possono esserci bug o cambiamenti radicali, che possono non essere retrocompatibili (breaking changes). -- โš ๏ธ **Non usare lโ€™app come unico modo per archiviare le tue foto e i tuoi video.** -- โš ๏ธ Segui sempre la regola di backup [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) per proteggere i tuoi ricordi e le foto a cui tieni! +> [!WARNING] +> โš ๏ธ Segui sempre la regola di backup [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) per proteggere i tuoi ricordi e le foto a cui tieni! +> > [!NOTE] > La documentazione principale, comprese le guide allโ€™installazione, si trova su https://immich.app/. diff --git a/readme_i18n/README_ja_JP.md b/readme_i18n/README_ja_JP.md index 0e74077895..8c2088247a 100644 --- a/readme_i18n/README_ja_JP.md +++ b/readme_i18n/README_ja_JP.md @@ -36,27 +36,24 @@ เธ เธฒเธฉเธฒเน„เธ—เธข

-## ๅ…่ฒฌไบ‹้ … +> [!WARNING] +> โš ๏ธ ๅคงๅˆ‡ใชๅ†™็œŸใ‚„ใƒ“ใƒ‡ใ‚ชใฏใ€ๅธธใซ [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) ใฎใƒใƒƒใ‚ฏใ‚ขใƒƒใƒ—ใƒ—ใƒฉใƒณใซๅพ“ใฃใฆใใ ใ•ใ„๏ผ +> -- โš ๏ธ ใ“ใฎใƒ—ใƒญใ‚ธใ‚งใ‚ฏใƒˆใฏ **้žๅธธใซๆดป็™บใซ** ้–‹็™บไธญใงใ™ใ€‚ -- โš ๏ธ ใƒใ‚ฐใฎๅญ˜ๅœจใ‚„ๅค‰ๆ›ดใŒๅ…ฅใ‚‹ใ“ใจใ‚‚ไบˆๆƒณใ•ใ‚Œใพใ™ใ€‚ -- โš ๏ธ **ๅ†™็œŸใ‚„ใƒ“ใƒ‡ใ‚ชใ‚’ไฟๅญ˜ใ™ใ‚‹ๅ”ฏไธ€ใฎๆ–นๆณ•ใจใ—ใฆใ“ใฎใ‚ขใƒ—ใƒชใ‚’ไฝฟ็”จใ—ใชใ„ใงใใ ใ•ใ„ใ€‚** -- โš ๏ธ ๅคงๅˆ‡ใชๅ†™็œŸใ‚„ใƒ“ใƒ‡ใ‚ชใฏใ€ๅธธใซ [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) ใฎใƒใƒƒใ‚ฏใ‚ขใƒƒใƒ—ใƒ—ใƒฉใƒณใซๅพ“ใฃใฆใใ ใ•ใ„๏ผ +> [!NOTE] +> ใ‚คใƒณใ‚นใƒˆใƒผใƒซใ‚ฌใ‚คใƒ‰ใ‚’ๅซใ‚€ไธปใชใƒ‰ใ‚ญใƒฅใƒกใƒณใƒˆใฏใ€https://immich.app/ ใงใ™ใ€‚ ## ใ‚ณใƒณใƒ†ใƒณใƒ„ -- [ๅ…ฌๅผใƒ‰ใ‚ญใƒฅใƒกใƒณใƒˆ](https://docs.immich.app) -- [ใƒญใƒผใƒ‰ใƒžใƒƒใƒ—](https://github.com/orgs/immich-app/projects/1) -- [ใƒ‡ใƒข](#ใƒ‡ใƒข) -- [ๆฉŸ่ƒฝ](#ๆฉŸ่ƒฝ) +- [ๅ…ฌๅผใƒ‰ใ‚ญใƒฅใƒกใƒณใƒˆ](https://docs.immich.app/) - [็ดนไป‹](https://docs.immich.app/overview/introduction) - [ใ‚คใƒณใ‚นใƒˆใƒผใƒซ](https://docs.immich.app/install/requirements) +- [ใƒญใƒผใƒ‰ใƒžใƒƒใƒ—](https://immich.app/roadmap) +- [ใƒ‡ใƒข](#ใƒ‡ใƒข) +- [ๆฉŸ่ƒฝ](#ๆฉŸ่ƒฝ) +- [็ฟป่จณ](https://docs.immich.app/developer/translations) - [ใ‚ณใƒณใƒˆใƒชใƒ“ใƒฅใƒผใ‚ทใƒงใƒณใ‚ฌใ‚คใƒ‰](https://docs.immich.app/overview/support-the-project) -## ใƒ‰ใ‚ญใƒฅใƒกใƒณใƒˆ - -ใ‚คใƒณใ‚นใƒˆใƒผใƒซใ‚ฌใ‚คใƒ‰ใ‚’ๅซใ‚€ไธปใชใƒ‰ใ‚ญใƒฅใƒกใƒณใƒˆใฏใ€https://immich.app/ ใงใ™ใ€‚ - ## ใƒ‡ใƒข web ใƒ‡ใƒขใฏ https://demo.immich.app ใ‹ใ‚‰ใ‚ขใ‚ฏใ‚ปใ‚นใงใใพใ™ใ€‚ใƒขใƒใ‚คใƒซใ‚ขใƒ—ใƒชใฎๅ ดๅˆใ€`Server Endpoint URL` ใซใฏ `https://demo.immich.app` ใ‚’ไฝฟ็”จใ™ใ‚‹ใ“ใจใŒใงใใพใ™ diff --git a/readme_i18n/README_ko_KR.md b/readme_i18n/README_ko_KR.md index c2dfd11dd3..66ff4c79c3 100644 --- a/readme_i18n/README_ko_KR.md +++ b/readme_i18n/README_ko_KR.md @@ -39,12 +39,9 @@

-## ์ฃผ์˜ ์‚ฌํ•ญ - -- โš ๏ธ ์ด ํ”„๋กœ์ ํŠธ๋Š” **๋งค์šฐ ํ™œ๋ฐœํ•˜๊ฒŒ** ๊ฐœ๋ฐœ ์ค‘์ž…๋‹ˆ๋‹ค. -- โš ๏ธ ๋ฒ„๊ทธ์™€ ์žฆ์€ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์ด ์žˆ์„ ๊ฒƒ์œผ๋กœ ์˜ˆ์ƒ๋ฉ๋‹ˆ๋‹ค. -- โš ๏ธ **์‚ฌ์ง„๊ณผ ๋™์˜์ƒ์„ ์ด ์•ฑ์—๋งŒ ๋‹จ๋…์œผ๋กœ ์ €์žฅํ•˜์ง€ ๋งˆ์„ธ์š”.** -- โš ๏ธ ์ค‘์š”ํ•œ ์‚ฌ์ง„๊ณผ ๋™์˜์ƒ์„ ์œ„ํ•ด ํ•ญ์ƒ [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) ๋ฐฑ์—… ๊ณ„ํš์„ ๋”ฐ๋ฅด์„ธ์š”! +> [!WARNING] +> โš ๏ธ ์ค‘์š”ํ•œ ์‚ฌ์ง„๊ณผ ๋™์˜์ƒ์„ ์œ„ํ•ด ํ•ญ์ƒ [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) ๋ฐฑ์—… ๊ณ„ํš์„ ๋”ฐ๋ฅด์„ธ์š”! +> > [!NOTE] > ์„ค์น˜ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ํฌํ•จํ•œ ์ฃผ์š” ๋ฌธ์„œ๋Š” https://immich.app/ ์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. @@ -57,7 +54,7 @@ - [๋กœ๋“œ๋งต](https://immich.app/roadmap) - [๋ฐ๋ชจ](#๋ฐ๋ชจ) - [๊ธฐ๋Šฅ](#๊ธฐ๋Šฅ) -- [๋ฒˆ์—ญ](https://docs.immich.app/developer/tranlations) +- [๋ฒˆ์—ญ](https://docs.immich.app/developer/translations) - [๊ธฐ์—ฌ](https://docs.immich.app/overview/support-the-project) ## ๋ฐ๋ชจ diff --git a/readme_i18n/README_nl_NL.md b/readme_i18n/README_nl_NL.md index ac72e9d238..ef7591b618 100644 --- a/readme_i18n/README_nl_NL.md +++ b/readme_i18n/README_nl_NL.md @@ -37,27 +37,24 @@ เธ เธฒเธฉเธฒเน„เธ—เธข

-## Disclaimer +> [!WARNING] +> โš ๏ธ Volg altijd het [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) backup plan voor je kostbare foto's en video's! +> -- โš ๏ธ Het project wordt momenteel **zeer actief** ontwikkeld. -- โš ๏ธ Verwacht bugs en ingrijpende wijzigingen. -- โš ๏ธ **Gebruik de app niet als de enige manier om uw foto's en video's op te slaan.** -- โš ๏ธ Volg altijd het [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) backup plan voor je kostbare foto's en video's! +> [!NOTE] +> De belangrijkste documentatie, inclusief installatie handleidingen, zijn te vinden op https://immich.app/. ## Inhoud -- [Officiรซle documentatie](https://docs.immich.app) -- [Toekomstplannen](https://github.com/orgs/immich-app/projects/1) -- [Demo](#demo) -- [Functies](#functies) +- [Officiรซle documentatie](https://docs.immich.app/) - [Introductie](https://docs.immich.app/overview/introduction) - [Installatie](https://docs.immich.app/install/requirements) +- [Toekomstplannen](https://immich.app/roadmap) +- [Demo](#demo) +- [Functies](#functies) +- [Vertalingen](https://docs.immich.app/developer/translations) - [Richtlijnen voor bijdragen](https://docs.immich.app/overview/support-the-project) -## Documentatie - -De belangrijkste documentatie, inclusief installatie handleidingen, zijn te vinden op https://immich.app/. - ## Demo Je kunt de demo [hier](https://demo.immich.app/) bekijken. Voor de mobiele app kun je gebruik maken van `https://demo.immich.app` voor de `Server Endpoint URL`. diff --git a/readme_i18n/README_pt_BR.md b/readme_i18n/README_pt_BR.md index d6f51cd779..1e1861a797 100644 --- a/readme_i18n/README_pt_BR.md +++ b/readme_i18n/README_pt_BR.md @@ -40,16 +40,9 @@

-## Avisos - -- โš ๏ธ Este projeto estรก sob **desenvolvimento constante**. -- โš ๏ธ Podem ocorrer bugs e _breaking changes_ (alteraรงรตes que quebram a - compatibilidade com versรตes anteriores). -- โš ๏ธ **Nรฃo use esta soluรงรฃo como a รบnica forma de fazer backup das suas fotos e - vรญdeos.** -- โš ๏ธ Sempre siga o plano - [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) de backup - para as suas mรญdias preciosas! +> [!WARNING] +> โš ๏ธ Sempre siga o plano [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) de backup para as suas mรญdias preciosas! +> > [!NOTE] > Vocรช pode encontrar a documentaรงรฃo principal, incluindo guias de instalaรงรฃo, em https://immich.app/. diff --git a/readme_i18n/README_ru_RU.md b/readme_i18n/README_ru_RU.md index 9c60e5f772..765e876f37 100644 --- a/readme_i18n/README_ru_RU.md +++ b/readme_i18n/README_ru_RU.md @@ -39,13 +39,9 @@ เธ เธฒเธฉเธฒเน„เธ—เธข

-## ะŸั€ะตะดัƒะฟั€ะตะถะดะตะฝะธะต - -- โš ๏ธ ะญั‚ะพั‚ ะฟั€ะพะตะบั‚ ะฝะฐั…ะพะดะธั‚ัั **ะฒ ะพั‡ะตะฝัŒ ะฐะบั‚ะธะฒะฝะพะน** ั€ะฐะทั€ะฐะฑะพั‚ะบะต. -- โš ๏ธ ะžะถะธะดะฐะนั‚ะต ะฝะตะดะพั€ะฐะฑะพั‚ะบะธ ะธ ะณะปะพะฑะฐะปัŒะฝั‹ะต ะธะทะผะตะฝะตะฝะธั. -- โš ๏ธ **ะะต ะธัะฟะพะปัŒะทัƒะนั‚ะต ัั‚ะพ ะฟั€ะธะปะพะถะตะฝะธะต ะบะฐะบ ะตะดะธะฝัั‚ะฒะตะฝะฝะพะต ั…ั€ะฐะฝะธะปะธั‰ะต ัะฒะพะธั… ั„ะพั‚ะพ ะธ ะฒะธะดะตะพ.** -- โš ๏ธ ะ’ัะตะณะดะฐ ัะปะตะดัƒะนั‚ะต [ะฟะปะฐะฝัƒ ั€ะตะทะตั€ะฒะฝะพะณะพ ะบะพะฟะธั€ะพะฒะฐะฝะธั ยซ3-2-1ยป](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/ "ะกั‚ั€ะฐั‚ะตะณะธะธ ั€ะตะทะตั€ะฒะฝะพะณะพ ะบะพะฟะธั€ะพะฒะฐะฝะธั: ะŸะพั‡ะตะผัƒ ัั‚ั€ะฐั‚ะตะณะธั ั€ะตะทะตั€ะฒะฝะพะณะพ ะบะพะฟะธั€ะพะฒะฐะฝะธั ยซ3-2-1ยป โ€” ะปัƒั‡ัˆะฐั") ะดะปั ะฒะฐัˆะธั… ะดั€ะฐะณะพั†ะตะฝะฝั‹ั… ั„ะพั‚ะพะณั€ะฐั„ะธะน ะธ ะฒะธะดะตะพ! - +> [!WARNING] +> โš ๏ธ ะ’ัะตะณะดะฐ ัะปะตะดัƒะนั‚ะต [ะฟะปะฐะฝัƒ ั€ะตะทะตั€ะฒะฝะพะณะพ ะบะพะฟะธั€ะพะฒะฐะฝะธั ยซ3-2-1ยป](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) ะดะปั ะฒะฐัˆะธั… ะดั€ะฐะณะพั†ะตะฝะฝั‹ั… ั„ะพั‚ะพะณั€ะฐั„ะธะน ะธ ะฒะธะดะตะพ! +> > [!NOTE] > ะ˜ะฝัั‚ั€ัƒะบั†ะธะธ ะฟะพ ัƒัั‚ะฐะฝะพะฒะบะต ะธ ะดะพะบัƒะผะตะฝั‚ะฐั†ะธั ะฟะพ ััั‹ะปะบะต https://immich.app/ @@ -55,7 +51,7 @@ - [ะžั„ะธั†ะธะฐะปัŒะฝะฐั ะดะพะบัƒะผะตะฝั‚ะฐั†ะธั](https://docs.immich.app) - [ะ’ะฒะตะดะตะฝะธะต](https://docs.immich.app/overview/introduction) - [ะฃัั‚ะฐะฝะพะฒะบะฐ](https://docs.immich.app/install/requirements) -- [ะŸะปะฐะฝ ั€ะฐะทั€ะฐะฑะพั‚ะบะธ](https://github.com/orgs/immich-app/projects/1) +- [ะŸะปะฐะฝ ั€ะฐะทั€ะฐะฑะพั‚ะบะธ](https://immich.app/roadmap) - [ะ”ะตะผะพ](#demo) - [ะ’ะพะทะผะพะถะฝะพัั‚ะธ](#features) - [ะŸะตั€ะตะฒะพะด](https://docs.immich.app/developer/translations) diff --git a/readme_i18n/README_sv_SE.md b/readme_i18n/README_sv_SE.md index a421c23c2e..b3a3cc804f 100644 --- a/readme_i18n/README_sv_SE.md +++ b/readme_i18n/README_sv_SE.md @@ -38,27 +38,24 @@ เธ เธฒเธฉเธฒเน„เธ—เธข

-## Ansvarsfriskrivning +> [!WARNING] +> โš ๏ธ Tillรคmpa alltid [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/)-strategin fรถr sรคkerhetskopiering av dina foton och videor! +> -- โš ๏ธ Projektet รคr under **mycket aktiv** utveckling. -- โš ๏ธ Fรถrvรคnta dig buggar och brytande fรถrรคndringar. -- โš ๏ธ **Anvรคnd inte appen som enda lagringssรคtt fรถr dina foton och videor.** -- โš ๏ธ Tillรคmpa alltid [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/)-strategin fรถr sรคkerhetskopiering av dina foton och videor! +> [!NOTE] +> Dokumentation och installationsguider hittas pรฅ https://immich.app/. ## Innehรฅll -- [Officiell Dokumentation](https://docs.immich.app) -- [Roadmap](https://github.com/orgs/immich-app/projects/1) -- [Demo](#demo) -- [Funktioner](#features) +- [Officiell Dokumentation](https://docs.immich.app/) - [Introduktion](https://docs.immich.app/overview/introduction) - [Installation](https://docs.immich.app/install/requirements) +- [Roadmap](https://immich.app/roadmap) +- [Demo](#demo) +- [Funktioner](#funktioner) +- [ร–versรคttningar](https://docs.immich.app/developer/translations) - [Riktlinjer fรถr Bidrag](https://docs.immich.app/overview/support-the-project) -## Dokumentation - -Dokumentation och installationsguider hittas pรฅ https://imiich.app/. - ## Demo Ett webb-demo finns att testa pรฅ https://demo.immich.app. Anvรคnd `https://demo.immich.app` i mobilappen som `Server Endpoint URL` diff --git a/readme_i18n/README_tr_TR.md b/readme_i18n/README_tr_TR.md index 46aef49745..5ca61df023 100644 --- a/readme_i18n/README_tr_TR.md +++ b/readme_i18n/README_tr_TR.md @@ -37,26 +37,24 @@ เธ เธฒเธฉเธฒเน„เธ—เธข

-## Feragatname +> [!WARNING] +> โš ๏ธ Always follow [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) backup plan for your precious photos and videos! +> -- โš ๏ธ Proje **รงok aktif** bir ลŸekilde geliลŸtirilmektedir. -- โš ๏ธ Hatalar ve uygulama yapฤฑsฤฑnฤฑ bozan deฤŸiลŸiklikler olabilir. -- โš ๏ธ **Uygulamayฤฑ, fotoฤŸraflarฤฑnฤฑzฤฑ ve videolarฤฑnฤฑzฤฑ saklamanฤฑn tek yรถntemi olarak kullanmayฤฑn!** +> [!NOTE] +> Kurulum dahil olmak รผzere resmi belgeleri https://immich.app/ adresinde bulabilirsiniz. -## Content +## BaฤŸlantฤฑlar -- [Resmi Belgeler](https://docs.immich.app) -- [Yol Haritasฤฑ](https://github.com/orgs/immich-app/projects/1) -- [Demo](#demo) -- [ร–zellikler](#รถzellikler) +- [Resmi Belgeler](https://docs.immich.app/) - [GiriลŸ](https://docs.immich.app/overview/introduction) - [Kurulum](https://docs.immich.app/install/requirements) +- [Yol Haritasฤฑ](https://immich.app/roadmap) +- [Demo](#demo) +- [ร–zellikler](#รถzellikler) +- [ร‡eviriler](https://docs.immich.app/developer/translations) - [Katkฤฑ SaฤŸlama Rehberi](https://docs.immich.app/overview/support-the-project) -## Belgeler - -Kurulum dahil olmak รผzere resmi belgeleri https://immich.app/ adresinde bulabilirsiniz. - ## Demo Web demo adresi: https://demo.immich.app. Mobil uygulama iรงin `Server Endpoint URL` olarak `https://demo.immich.app` adresini kullanabilirsiniz. diff --git a/readme_i18n/README_uk_UA.md b/readme_i18n/README_uk_UA.md index 297054ee42..6b5849b01c 100644 --- a/readme_i18n/README_uk_UA.md +++ b/readme_i18n/README_uk_UA.md @@ -39,12 +39,9 @@ เธ เธฒเธฉเธฒเน„เธ—เธข

-## ะ—ะฐัั‚ะตั€ะตะถะตะฝะฝั - -- โš ๏ธ ะฆะตะน ะฟั€ะพั”ะบั‚ ะฟะตั€ะตะฑัƒะฒะฐั” **ะฒ ะดัƒะถะต ะฐะบั‚ะธะฒะฝั–ะน** ั€ะพะทั€ะพะฑั†ั–. -- โš ๏ธ ะžั‡ั–ะบัƒะนั‚ะต ะฑะตะทะปั–ั‡ ะฟะพะผะธะปะพะบ ั– ะณะปะพะฑะฐะปัŒะฝะธั… ะทะผั–ะฝ. -- โš ๏ธ **ะะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะนั‚ะต ั†ะตะน ะทะฐัั‚ะพััƒะฝะพะบ ัะบ ั”ะดะธะฝะต ัั…ะพะฒะธั‰ะต ัะฒะพั—ั… ั„ะพั‚ะพ ั‚ะฐ ะฒั–ะดะตะพ.** -- โš ๏ธ ะ—ะฐะฒะถะดะธ ะดะพั‚ั€ะธะผัƒะนั‚ะตััŒ [ะฟะปะฐะฝัƒ ั€ะตะทะตั€ะฒะฝะพะณะพ ะบะพะฟั–ัŽะฒะฐะฝะฝั 3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) ะดะปั ะฒะฐัˆะธั… ะดะพั€ะพะณะพั†ั–ะฝะฝะธั… ั„ะพั‚ะพะณั€ะฐั„ั–ะน ั‚ะฐ ะฒั–ะดะตะพ! +> [!WARNING] +> โš ๏ธ ะ—ะฐะฒะถะดะธ ะดะพั‚ั€ะธะผัƒะนั‚ะตััŒ [ะฟะปะฐะฝัƒ ั€ะตะทะตั€ะฒะฝะพะณะพ ะบะพะฟั–ัŽะฒะฐะฝะฝั 3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) ะดะปั ะฒะฐัˆะธั… ะดะพั€ะพะณะพั†ั–ะฝะฝะธั… ั„ะพั‚ะพะณั€ะฐั„ั–ะน ั‚ะฐ ะฒั–ะดะตะพ! +> > [!NOTE] > ะžัะฝะพะฒะฝัƒ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ัŽ, ะทะพะบั€ะตะผะฐ ะฟะพัั–ะฑะฝะธะบะธ ะทั– ะฒัั‚ะฐะฝะพะฒะปะตะฝะฝั, ะผะพะถะฝะฐ ะทะฝะฐะนั‚ะธ ะทะฐ ะฐะดั€ะตัะพัŽ https://immich.app/. diff --git a/readme_i18n/README_vi_VN.md b/readme_i18n/README_vi_VN.md index b6b22ff610..54e8a9bb90 100644 --- a/readme_i18n/README_vi_VN.md +++ b/readme_i18n/README_vi_VN.md @@ -41,12 +41,9 @@

-## Tuyรชn bแป‘ miแป…n trแปซ trรกch nhiแป‡m - -- โš ๏ธ Dแปฑ รกn ฤ‘ang ฤ‘ฦฐแปฃc phรกt triแปƒn **rแบฅt tรญch cแปฑc**. -- โš ๏ธ Dแปฑ kiแบฟn โ€‹โ€‹sแบฝ cรณ lแป—i vร  thay ฤ‘แป•i ฤ‘แป™t ngแป™t. -- โš ๏ธ **Khรดng sแปญ dแปฅng แปฉng dแปฅng nhฦฐ lร  cรกch duy nhแบฅt ฤ‘แปƒ lฦฐu trแปฏ แบฃnh vร  video cแปงa bแบกn.** -- โš ๏ธ Luรดn tuรขn thแปง kแบฟ hoแบกch sao lฦฐu [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) cho nhแปฏng bแปฉc แบฃnh vร  video quรฝ giรก cแปงa bแบกn! +> [!WARNING] +> โš ๏ธ Luรดn tuรขn thแปง kแบฟ hoแบกch sao lฦฐu [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) cho nhแปฏng bแปฉc แบฃnh vร  video quรฝ giรก cแปงa bแบกn! +> > [!NOTE] > Bแบกn cรณ thแปƒ tรฌm thแบฅy tร i liแป‡u chรญnh, bao gแป“m hฦฐแป›ng dแบซn cร i ฤ‘แบทt, tแบกi https://immich.app/. diff --git a/readme_i18n/README_zh_CN.md b/readme_i18n/README_zh_CN.md index b48e69f94d..6bd82b13e2 100644 --- a/readme_i18n/README_zh_CN.md +++ b/readme_i18n/README_zh_CN.md @@ -43,12 +43,9 @@

-## ๅ…่ดฃๅฃฐๆ˜Ž - -- โš ๏ธ ๆœฌ้กน็›ฎๆญฃๅœจ **้žๅธธๆดป่ทƒ** ๅœฐๅผ€ๅ‘ไธญใ€‚ -- โš ๏ธ ๅฏ่ƒฝๅญ˜ๅœจ bug ๆˆ–่€…้šๆ—ถๆœ‰้‡ๅคงๅ˜ๆ›ดใ€‚ -- โš ๏ธ **ไธ่ฆๆŠŠๆœฌ่ฝฏไปถไฝœไธบๆ‚จๅญ˜ๅ‚จ็…ง็‰‡ๆˆ–่ง†้ข‘็š„ๅ”ฏไธ€ๆ–นๅผใ€‚** -- โš ๏ธ ไธบไบ†ๆ‚จๅฎ่ดต็š„็…ง็‰‡ไธŽ่ง†้ข‘๏ผŒ่ฏทๅง‹็ปˆ้ตๅฎˆ [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) ๅค‡ไปฝๆ–นๆกˆ๏ผ +> [!WARNING] +> โš ๏ธ ไธบไบ†ๆ‚จๅฎ่ดต็š„็…ง็‰‡ไธŽ่ง†้ข‘๏ผŒ่ฏทๅง‹็ปˆ้ตๅฎˆ [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) ๅค‡ไปฝๆ–นๆกˆ๏ผ +> > [!NOTE] > ๅฎŒๆ•ด็š„้กน็›ฎๆ–‡ๆกฃไปฅๅŠๅฎ‰่ฃ…ๆ•™็จ‹่ฏทๅ‚่ง๏ผšใ€‚ diff --git a/server/.nvmrc b/server/.nvmrc deleted file mode 100644 index 5bf4400f22..0000000000 --- a/server/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -24.15.0 diff --git a/server/Dockerfile b/server/Dockerfile index 50e8b9714c..16335febda 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -1,4 +1,4 @@ -FROM ghcr.io/immich-app/base-server-dev:202604141125@sha256:9338c216fb0fef4172cf53cd8e4ff607c6635d576dcc1366151f13d69bbb45ef AS builder +FROM ghcr.io/immich-app/base-server-dev:202606021219@sha256:63fa91aa011f6f2921dd32fe6d1be8d637e9bd7f3e3dd0c8e446afb31b282af4 AS builder ENV COREPACK_ENABLE_DOWNLOAD_PROMPT=0 \ CI=1 \ COREPACK_HOME=/tmp \ @@ -13,13 +13,15 @@ FROM builder AS server WORKDIR /usr/src/app COPY ./server ./server/ +COPY ./packages/sdk ./packages/sdk/ +COPY ./packages/plugin-sdk ./packages/plugin-sdk/ RUN --mount=type=cache,id=pnpm-server,target=/buildcache/pnpm-store \ --mount=type=bind,source=package.json,target=package.json \ --mount=type=bind,source=.pnpmfile.cjs,target=.pnpmfile.cjs \ --mount=type=bind,source=pnpm-lock.yaml,target=pnpm-lock.yaml \ --mount=type=bind,source=pnpm-workspace.yaml,target=pnpm-workspace.yaml \ - SHARP_IGNORE_GLOBAL_LIBVIPS=true pnpm --filter immich --frozen-lockfile build && \ - SHARP_FORCE_GLOBAL_LIBVIPS=true pnpm --filter immich --frozen-lockfile --prod --no-optional deploy /output/server-pruned + SHARP_IGNORE_GLOBAL_LIBVIPS=true pnpm --filter @immich/sdk --filter @immich/plugin-sdk --filter immich --frozen-lockfile build && \ + SHARP_FORCE_GLOBAL_LIBVIPS=true pnpm --filter immich --frozen-lockfile --prod --no-optional deploy /output/server-pruned FROM builder AS web @@ -29,7 +31,7 @@ ENV IMMICH_BUILD=${BUILD_ID} WORKDIR /usr/src/app COPY ./web ./web/ COPY ./i18n ./i18n/ -COPY ./open-api ./open-api/ +COPY ./packages ./packages/ RUN --mount=type=cache,id=pnpm-web,target=/buildcache/pnpm-store \ --mount=type=bind,source=package.json,target=package.json \ --mount=type=bind,source=.pnpmfile.cjs,target=.pnpmfile.cjs \ @@ -40,8 +42,7 @@ RUN --mount=type=cache,id=pnpm-web,target=/buildcache/pnpm-store \ FROM builder AS cli -COPY ./cli ./cli/ -COPY ./open-api ./open-api/ +COPY ./packages ./packages/ RUN --mount=type=cache,id=pnpm-cli,target=/buildcache/pnpm-store \ --mount=type=bind,source=package.json,target=package.json \ --mount=type=bind,source=.pnpmfile.cjs,target=.pnpmfile.cjs \ @@ -55,26 +56,31 @@ FROM builder AS plugins ARG TARGETPLATFORM -COPY --from=ghcr.io/jdx/mise:2026.3.12@sha256:0210678cbf58413806531a27adb2c7daf1c37238e56e8f7ea381d73521571775 /usr/local/bin/mise /usr/local/bin/mise +COPY --from=ghcr.io/jdx/mise:2026.5.18@sha256:5bb3311994fa78cef307ca3077cdb18f9551da0886371fc26ea91ab56220ffc5 /usr/local/bin/mise /usr/local/bin/mise -WORKDIR /usr/src/app -COPY ./plugins/mise.toml ./plugins/ -ENV MISE_TRUSTED_CONFIG_PATHS=/usr/src/app/plugins/mise.toml +WORKDIR /app +COPY ./mise.toml ./mise.toml +COPY ./packages/plugin-core/mise.toml ./packages/plugin-core/ +ENV MISE_TRUSTED_CONFIG_PATHS=/app/mise.toml ENV MISE_DATA_DIR=/buildcache/mise +ENV MISE_DISABLE_TOOLS=flutter RUN --mount=type=cache,id=mise-tools-${TARGETPLATFORM},target=/buildcache/mise \ - mise install --cd plugins + mise install + +COPY ./packages/sdk ./packages/sdk/ +COPY ./packages/plugin-core ./packages/plugin-core/ +COPY ./packages/plugin-sdk ./packages/plugin-sdk/ -COPY ./plugins ./plugins/ # Build plugins -RUN --mount=type=cache,id=pnpm-plugins,target=/buildcache/pnpm-store \ +RUN --mount=type=cache,id=pnpm-packages,target=/buildcache/pnpm-store \ --mount=type=bind,source=package.json,target=package.json \ --mount=type=bind,source=.pnpmfile.cjs,target=.pnpmfile.cjs \ --mount=type=bind,source=pnpm-lock.yaml,target=pnpm-lock.yaml \ --mount=type=bind,source=pnpm-workspace.yaml,target=pnpm-workspace.yaml \ --mount=type=cache,id=mise-tools-${TARGETPLATFORM},target=/buildcache/mise \ - cd plugins && mise run build + mise //:plugins -FROM ghcr.io/immich-app/base-server-prod:202604141125@sha256:3b05219afcda09cebfb8513743fc92cec1a3ae262249bfe0de6f90da21326991 +FROM ghcr.io/immich-app/base-server-prod:202606021219@sha256:6ef9ef5859492149af770a6c884b5e2ddbaeef99f8885ea5f2d9f73625a3d9ec WORKDIR /usr/src/app ENV NODE_ENV=production \ @@ -84,8 +90,8 @@ ENV NODE_ENV=production \ COPY --from=server /output/server-pruned ./server COPY --from=web /usr/src/app/web/build /build/www COPY --from=cli /output/cli-pruned ./cli -COPY --from=plugins /usr/src/app/plugins/dist /build/corePlugin/dist -COPY --from=plugins /usr/src/app/plugins/manifest.json /build/corePlugin/manifest.json +COPY --from=plugins /app/packages/plugin-core/dist /build/plugins/immich-plugin-core/dist +COPY --from=plugins /app/packages/plugin-core/manifest.json /build/plugins/immich-plugin-core/manifest.json RUN ln -s ../../cli/bin/immich server/bin/immich COPY LICENSE /licenses/LICENSE.txt COPY LICENSE /LICENSE diff --git a/server/Dockerfile.dev b/server/Dockerfile.dev index f8a70f03b1..06196ae143 100644 --- a/server/Dockerfile.dev +++ b/server/Dockerfile.dev @@ -1,14 +1,10 @@ # dev build -FROM ghcr.io/immich-app/base-server-dev:202604141125@sha256:9338c216fb0fef4172cf53cd8e4ff607c6635d576dcc1366151f13d69bbb45ef AS dev +FROM ghcr.io/immich-app/base-server-dev:202606021219@sha256:63fa91aa011f6f2921dd32fe6d1be8d637e9bd7f3e3dd0c8e446afb31b282af4 AS dev -ENV COREPACK_ENABLE_DOWNLOAD_PROMPT=0 \ - CI=1 \ - COREPACK_HOME=/tmp \ - PNPM_HOME=/buildcache/pnpm-store -RUN npm install --global corepack@latest && \ - corepack enable pnpm && \ - echo "devdir=/buildcache/node-gyp" >> /usr/local/etc/npmrc && \ +COPY --from=ghcr.io/jdx/mise:2026.5.18@sha256:5bb3311994fa78cef307ca3077cdb18f9551da0886371fc26ea91ab56220ffc5 /usr/local/bin/mise /usr/local/bin/mise + +RUN echo "devdir=/buildcache/node-gyp" >> /usr/local/etc/npmrc && \ echo "store-dir=/buildcache/pnpm-store" >> /usr/local/etc/npmrc && \ echo "cache-dir=/buildcache/pnpm-cache" >> /usr/local/etc/npmrc && \ echo "# Retry configuration - default is 2" >> /usr/local/etc/npmrc && \ @@ -16,12 +12,17 @@ RUN npm install --global corepack@latest && \ mkdir -p /buildcache/pnpm-store /buildcache/pnpm-cache /buildcache/node-gyp && \ chmod -R o+rw /buildcache +ENV CI=1 +ENV PNPM_HOME=/buildcache/pnpm-store +ENV MISE_DATA_DIR=/buildcache/mise +ENV MISE_TRUSTED_CONFIG_PATHS=/usr/src/app/mise.toml +ENV PATH="${MISE_DATA_DIR}/shims:${PATH}:/usr/src/app/server/bin:/usr/src/app/web/bin" +ENV IMMICH_ENV=development +ENV NVIDIA_DRIVER_CAPABILITIES=all +ENV NVIDIA_VISIBLE_DEVICES=all + WORKDIR /usr/src/app -ENV PATH="${PATH}:/usr/src/app/server/bin:/usr/src/app/web/bin" \ - IMMICH_ENV=development \ - NVIDIA_DRIVER_CAPABILITIES=all \ - NVIDIA_VISIBLE_DEVICES=all ENTRYPOINT ["tini", "--", "/bin/bash", "-c"] FROM dev AS dev-container-server diff --git a/server/mise.toml b/server/mise.toml index d2240c4289..1462af0c52 100644 --- a/server/mise.toml +++ b/server/mise.toml @@ -33,9 +33,9 @@ env._.path = "./node_modules/.bin" run = "tsc --noEmit" [tasks.sql] -run = "node ./dist/bin/sync-open-api.js" +run = "node ./dist/bin/sync-sql.js" -[tasks."open-api"] +[tasks."sync-open-api"] run = "node ./dist/bin/sync-open-api.js" [tasks.migrations] @@ -55,12 +55,26 @@ run = [ env._.path = "./node_modules/.bin" run = "email dev -p 3050 --dir src/emails" -[tasks.checklist] +[tasks.ci-unit] run = [ { task = ":install" }, + { task = "//:plugins" }, { task = ":format" }, { task = ":lint" }, { task = ":check" }, - { task = ":test-medium --run" }, { task = ":test --run" }, ] + +[tasks.ci-medium] +run = [ + { task = ":install" }, + { task = "//:plugins" }, + { task = "//packages/plugin-core:build" }, + { task = ":test-medium --run" }, +] + +[tasks.checklist] +run = [ + { task = ":ci-unit" }, + { task = ":ci-medium" }, +] diff --git a/server/package.json b/server/package.json index ea8e327a0a..2aca79a0dc 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "immich", - "version": "2.7.5", + "version": "3.0.0", "description": "", "author": "", "private": true, @@ -33,12 +33,11 @@ "migrations:revert": "sql-tools -u ${DB_URL:-postgres://postgres:postgres@localhost:5432/immich} migrations revert", "schema:drop": "sql-tools -u ${DB_URL:-postgres://postgres:postgres@localhost:5432/immich} query 'DROP schema public cascade; CREATE schema public;'", "schema:reset": "pnpm run schema:drop && pnpm run migrations:run", - "sync:open-api": "node ./dist/bin/sync-open-api.js", - "sync:sql": "node ./dist/bin/sync-sql.js", "email:dev": "email dev -p 3050 --dir src/emails" }, "dependencies": { "@extism/extism": "2.0.0-rc13", + "@immich/plugin-sdk": "workspace:*", "@immich/sql-tools": "^0.5.1", "@nestjs/bullmq": "^11.0.1", "@nestjs/common": "^11.0.4", @@ -50,14 +49,14 @@ "@nestjs/websockets": "^11.0.4", "@opentelemetry/api": "^1.9.0", "@opentelemetry/context-async-hooks": "^2.0.0", - "@opentelemetry/exporter-prometheus": "^0.215.0", - "@opentelemetry/instrumentation-http": "^0.215.0", - "@opentelemetry/instrumentation-ioredis": "^0.63.0", - "@opentelemetry/instrumentation-nestjs-core": "^0.61.0", - "@opentelemetry/instrumentation-pg": "^0.67.0", + "@opentelemetry/exporter-prometheus": "^0.218.0", + "@opentelemetry/instrumentation-http": "^0.218.0", + "@opentelemetry/instrumentation-ioredis": "^0.66.0", + "@opentelemetry/instrumentation-nestjs-core": "^0.64.0", + "@opentelemetry/instrumentation-pg": "^0.70.0", "@opentelemetry/resources": "^2.0.1", "@opentelemetry/sdk-metrics": "^2.0.1", - "@opentelemetry/sdk-node": "^0.215.0", + "@opentelemetry/sdk-node": "^0.218.0", "@opentelemetry/semantic-conventions": "^1.34.0", "@react-email/components": "^1.0.0", "@react-email/render": "^2.0.0", @@ -72,10 +71,11 @@ "cookie": "^1.0.2", "cookie-parser": "^1.4.7", "cron": "4.4.0", - "exiftool-vendored": "^35.0.0", + "exiftool-vendored": "^35.20.0", "express": "^5.1.0", "fast-glob": "^3.3.2", "fluent-ffmpeg": "^2.1.2", + "generic-pool": "^3.9.0", "geo-tz": "^8.0.0", "handlebars": "^4.7.8", "helmet": "^8.1.0", @@ -84,7 +84,7 @@ "jose": "^6.0.0", "js-yaml": "^4.1.0", "jsonwebtoken": "^9.0.2", - "kysely": "0.28.16", + "kysely": "0.28.17", "kysely-postgres-js": "^3.0.0", "lodash": "^4.17.21", "luxon": "^3.4.2", @@ -93,7 +93,7 @@ "nest-commander": "^3.16.0", "nestjs-cls": "^6.0.0", "nestjs-kysely": "3.1.2", - "nestjs-otel": "^7.0.0", + "nestjs-otel": "^8.0.0", "nestjs-zod": "^5.3.0", "nodemailer": "^8.0.0", "openid-client": "^6.3.3", @@ -106,7 +106,7 @@ "reflect-metadata": "^0.2.0", "rxjs": "^7.8.1", "sanitize-filename": "^1.6.3", - "semver": "^7.6.2", + "semver": "^7.8.1", "sharp": "^0.34.5", "sirv": "^3.0.0", "socket.io": "^4.8.1", @@ -116,7 +116,7 @@ "ua-parser-js": "^2.0.0", "uuid": "^14.0.0", "validator": "^13.12.0", - "zod": "^4.3.6" + "zod": "4.3.6" }, "devDependencies": { "@eslint/js": "^10.0.0", @@ -138,7 +138,7 @@ "@types/luxon": "^3.6.2", "@types/mock-fs": "^4.13.1", "@types/multer": "^2.0.0", - "@types/node": "^24.12.2", + "@types/node": "^24.12.4", "@types/nodemailer": "^8.0.0", "@types/picomatch": "^4.0.0", "@types/pngjs": "^6.0.5", @@ -147,7 +147,7 @@ "@types/supertest": "^7.0.0", "@types/ua-parser-js": "^0.7.36", "@types/validator": "^13.15.2", - "@vitest/coverage-v8": "^3.0.0", + "@vitest/coverage-v8": "^4.0.0", "eslint": "^10.0.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-prettier": "^5.1.3", @@ -160,16 +160,13 @@ "sql-formatter": "^15.0.0", "supertest": "^7.1.0", "tailwindcss": "^3.4.0", - "testcontainers": "^11.0.0", + "testcontainers": "^12.0.0", "typescript": "^6.0.0", "typescript-eslint": "^8.28.0", "unplugin-swc": "^1.4.5", "vite-tsconfig-paths": "^6.0.0", "vitest": "^3.0.0" }, - "volta": { - "node": "24.15.0" - }, "overrides": { "sharp": "^0.34.5" } diff --git a/server/src/config.ts b/server/src/config.ts index 476b0eb160..730663d046 100644 --- a/server/src/config.ts +++ b/server/src/config.ts @@ -1,4 +1,5 @@ import { CronExpression } from '@nestjs/schedule'; +import { ReleaseChannel } from 'src/dtos/system-config.dto'; import { AudioCodec, Colorspace, @@ -45,6 +46,9 @@ export type SystemConfig = { accel: TranscodeHardwareAcceleration; accelDecode: boolean; tonemap: ToneMapping; + realtime: { + enabled: boolean; + }; }; job: Record; logging: { @@ -135,6 +139,7 @@ export type SystemConfig = { }; newVersionCheck: { enabled: boolean; + channel: ReleaseChannel; }; nightlyTasks: { startTime: string; @@ -223,7 +228,10 @@ export const defaults = Object.freeze({ transcode: TranscodePolicy.Required, tonemap: ToneMapping.Hable, accel: TranscodeHardwareAcceleration.Disabled, - accelDecode: false, + accelDecode: true, + realtime: { + enabled: false, + }, }, job: { [QueueName.BackgroundTask]: { concurrency: 5 }, @@ -344,6 +352,7 @@ export const defaults = Object.freeze({ }, newVersionCheck: { enabled: true, + channel: ReleaseChannel.Stable, }, nightlyTasks: { startTime: '00:00', diff --git a/server/src/constants.ts b/server/src/constants.ts index c771c1a995..16b7731dce 100644 --- a/server/src/constants.ts +++ b/server/src/constants.ts @@ -1,7 +1,15 @@ import { readFileSync } from 'node:fs'; import { dirname, join } from 'node:path'; import { SemVer } from 'semver'; -import { ApiTag, AudioCodec, DatabaseExtension, ExifOrientation, VectorIndex } from 'src/enum'; +import { + ApiTag, + AudioCodec, + DatabaseExtension, + ExifOrientation, + TranscodeHardwareAcceleration, + VectorIndex, + VideoCodec, +} from 'src/enum'; export const IMMICH_SERVER_START = 'Immich Server is listening'; @@ -199,7 +207,35 @@ export const endpointTags: Record = { export const AUDIO_ENCODER: Record = { [AudioCodec.Aac]: 'aac', [AudioCodec.Mp3]: 'mp3', - [AudioCodec.Libopus]: 'libopus', [AudioCodec.Opus]: 'libopus', [AudioCodec.PcmS16le]: 'pcm_s16le', }; + +export const SUPPORTED_HWA_CODECS: Record = { + [TranscodeHardwareAcceleration.Nvenc]: [VideoCodec.H264, VideoCodec.Hevc, VideoCodec.Av1], + [TranscodeHardwareAcceleration.Qsv]: [VideoCodec.H264, VideoCodec.Hevc, VideoCodec.Vp9, VideoCodec.Av1], + [TranscodeHardwareAcceleration.Vaapi]: [VideoCodec.H264, VideoCodec.Hevc, VideoCodec.Vp9, VideoCodec.Av1], + [TranscodeHardwareAcceleration.Rkmpp]: [VideoCodec.H264, VideoCodec.Hevc], + [TranscodeHardwareAcceleration.Disabled]: [VideoCodec.H264, VideoCodec.Hevc, VideoCodec.Vp9, VideoCodec.Av1], +}; + +export const HLS_BACKPRESSURE_PAUSE_SEGMENTS = 30; +export const HLS_BACKPRESSURE_RESUME_SEGMENTS = 15; +export const HLS_CLEANUP_INTERVAL_MS = 60 * 1000; +export const HLS_INACTIVITY_TIMEOUT_MS = 5 * 60 * 1000; +export const HLS_LEASE_DURATION_MS = 30 * 60 * 1000; +export const HLS_PLAYLIST_CONTENT_TYPE = 'application/vnd.apple.mpegurl'; +export const HLS_SEGMENT_DURATION = 2; +export const HLS_SEGMENT_FILENAME_REGEX = /^seg_(\d+)\.m4s$/; +export const HLS_VARIANTS = [ + { resolution: 480, codec: VideoCodec.Av1, bitrate: 1_000_000, codecString: 'av01.0.04M.08' }, + { resolution: 480, codec: VideoCodec.Hevc, bitrate: 1_200_000, codecString: 'hvc1.1.6.L90.B0' }, + { resolution: 480, codec: VideoCodec.H264, bitrate: 2_500_000, codecString: 'avc1.64001e' }, + { resolution: 720, codec: VideoCodec.Av1, bitrate: 2_000_000, codecString: 'av01.0.08M.08' }, + { resolution: 720, codec: VideoCodec.Hevc, bitrate: 2_500_000, codecString: 'hvc1.1.6.L93.B0' }, + { resolution: 720, codec: VideoCodec.H264, bitrate: 5_000_000, codecString: 'avc1.64001f' }, + { resolution: 1080, codec: VideoCodec.Av1, bitrate: 4_000_000, codecString: 'av01.0.09M.08' }, + { resolution: 1080, codec: VideoCodec.Hevc, bitrate: 4_500_000, codecString: 'hvc1.1.6.L120.B0' }, + { resolution: 1080, codec: VideoCodec.H264, bitrate: 8_000_000, codecString: 'avc1.640028' }, +]; +export const HLS_VERSION = 7; diff --git a/server/src/controllers/album.controller.spec.ts b/server/src/controllers/album.controller.spec.ts index 0c7a4eb09f..2ab7b08ceb 100644 --- a/server/src/controllers/album.controller.spec.ts +++ b/server/src/controllers/album.controller.spec.ts @@ -25,11 +25,11 @@ describe(AlbumController.name, () => { }); it('should reject an invalid shared param', async () => { - const { status, body } = await request(ctx.getHttpServer()).get('/albums?shared=invalid'); + const { status, body } = await request(ctx.getHttpServer()).get('/albums?isShared=invalid'); expect(status).toEqual(400); expect(body).toEqual( factory.responses.validationError([ - { path: ['shared'], message: 'Invalid option: expected one of "true"|"false"' }, + { path: ['isShared'], message: 'Invalid option: expected one of "true"|"false"' }, ]), ); }); diff --git a/server/src/controllers/duplicate.controller.ts b/server/src/controllers/duplicate.controller.ts index 0a8c451ed4..6502c3b2a9 100644 --- a/server/src/controllers/duplicate.controller.ts +++ b/server/src/controllers/duplicate.controller.ts @@ -41,8 +41,8 @@ export class DuplicateController { @Authenticated({ permission: Permission.DuplicateDelete }) @HttpCode(HttpStatus.NO_CONTENT) @Endpoint({ - summary: 'Delete a duplicate', - description: 'Delete a single duplicate asset specified by its ID.', + summary: 'Dismiss a duplicate group', + description: 'Dismiss a duplicate group by its ID, unlinking all assets in the group without deleting them.', history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), }) deleteDuplicate(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { diff --git a/server/src/controllers/index.ts b/server/src/controllers/index.ts index dc3754ce24..336ea1cf91 100644 --- a/server/src/controllers/index.ts +++ b/server/src/controllers/index.ts @@ -35,6 +35,7 @@ import { TimelineController } from 'src/controllers/timeline.controller'; import { TrashController } from 'src/controllers/trash.controller'; import { UserAdminController } from 'src/controllers/user-admin.controller'; import { UserController } from 'src/controllers/user.controller'; +import { VideoStreamController } from 'src/controllers/video-stream.controller'; import { ViewController } from 'src/controllers/view.controller'; import { WorkflowController } from 'src/controllers/workflow.controller'; @@ -76,6 +77,7 @@ export const controllers = [ TrashController, UserAdminController, UserController, + VideoStreamController, ViewController, WorkflowController, ]; diff --git a/server/src/controllers/person.controller.spec.ts b/server/src/controllers/person.controller.spec.ts index cf3a5e56b0..64fe4f3554 100644 --- a/server/src/controllers/person.controller.spec.ts +++ b/server/src/controllers/person.controller.spec.ts @@ -53,16 +53,6 @@ describe(PersonController.name, () => { await request(ctx.getHttpServer()).post('/people'); expect(ctx.authenticate).toHaveBeenCalled(); }); - - it('should map an empty birthDate to null', async () => { - await request(ctx.getHttpServer()).post('/people').send({ birthDate: '' }); - expect(service.create).toHaveBeenCalledWith(undefined, { birthDate: null }); - }); - - it('should map an empty color to null', async () => { - await request(ctx.getHttpServer()).post('/people').send({ color: '' }); - expect(service.create).toHaveBeenCalledWith(undefined, { color: null }); - }); }); describe('DELETE /people', () => { @@ -153,12 +143,6 @@ describe(PersonController.name, () => { ); }); - it('should map an empty birthDate to null', async () => { - const id = factory.uuid(); - await request(ctx.getHttpServer()).put(`/people/${id}`).send({ birthDate: '' }); - expect(service.update).toHaveBeenCalledWith(undefined, id, { birthDate: null }); - }); - it('should not accept an invalid birth date (false)', async () => { const { status, body } = await request(ctx.getHttpServer()) .put(`/people/${factory.uuid()}`) diff --git a/server/src/controllers/plugin.controller.spec.ts b/server/src/controllers/plugin.controller.spec.ts new file mode 100644 index 0000000000..881a7dd953 --- /dev/null +++ b/server/src/controllers/plugin.controller.spec.ts @@ -0,0 +1,56 @@ +import { PluginController } from 'src/controllers/plugin.controller'; +import { LoggingRepository } from 'src/repositories/logging.repository'; +import { PluginService } from 'src/services/plugin.service'; +import request from 'supertest'; +import { errorDto } from 'test/medium/responses'; +import { factory } from 'test/small.factory'; +import { automock, ControllerContext, controllerSetup, mockBaseService } from 'test/utils'; + +describe(PluginController.name, () => { + let ctx: ControllerContext; + const service = mockBaseService(PluginService); + + beforeAll(async () => { + ctx = await controllerSetup(PluginController, [ + { provide: PluginService, useValue: service }, + { provide: LoggingRepository, useValue: automock(LoggingRepository, { strict: false }) }, + ]); + return () => ctx.close(); + }); + + beforeEach(() => { + service.resetAllMocks(); + ctx.reset(); + }); + + describe('GET /plugins', () => { + it('should be an authenticated route', async () => { + await request(ctx.getHttpServer()).get('/plugins'); + expect(ctx.authenticate).toHaveBeenCalled(); + }); + + it(`should require id to be a uuid`, async () => { + const { status, body } = await request(ctx.getHttpServer()) + .get(`/plugins`) + .query({ id: 'invalid' }) + .set('Authorization', `Bearer token`); + expect(status).toBe(400); + expect(body).toEqual(errorDto.validationError([{ path: ['id'], message: 'Invalid UUID' }])); + }); + }); + + describe('GET /plugins/:id', () => { + it('should be an authenticated route', async () => { + await request(ctx.getHttpServer()).get(`/plugins/${factory.uuid()}`); + expect(ctx.authenticate).toHaveBeenCalled(); + }); + + it(`should require id to be a uuid`, async () => { + const { status, body } = await request(ctx.getHttpServer()) + .get(`/plugins/invalid`) + .set('Authorization', `Bearer token`); + expect(status).toBe(400); + expect(body).toEqual(errorDto.validationError([{ path: ['id'], message: 'Invalid UUID' }])); + }); + }); +}); diff --git a/server/src/controllers/plugin.controller.ts b/server/src/controllers/plugin.controller.ts index 52c833e93d..10eb4ba514 100644 --- a/server/src/controllers/plugin.controller.ts +++ b/server/src/controllers/plugin.controller.ts @@ -1,7 +1,13 @@ -import { Controller, Get, Param } from '@nestjs/common'; +import { Controller, Get, Param, Query } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { Endpoint, HistoryBuilder } from 'src/decorators'; -import { PluginResponseDto, PluginTriggerResponseDto } from 'src/dtos/plugin.dto'; +import { + PluginMethodResponseDto, + PluginMethodSearchDto, + PluginResponseDto, + PluginSearchDto, + PluginTemplateResponseDto, +} from 'src/dtos/plugin.dto'; import { Permission } from 'src/enum'; import { Authenticated } from 'src/middleware/auth.guard'; import { PluginService } from 'src/services/plugin.service'; @@ -12,26 +18,37 @@ import { UUIDParamDto } from 'src/validation'; export class PluginController { constructor(private service: PluginService) {} - @Get('triggers') - @Authenticated({ permission: Permission.PluginRead }) - @Endpoint({ - summary: 'List all plugin triggers', - description: 'Retrieve a list of all available plugin triggers.', - history: new HistoryBuilder().added('v2.3.0').alpha('v2.3.0'), - }) - getPluginTriggers(): PluginTriggerResponseDto[] { - return this.service.getTriggers(); - } - @Get() @Authenticated({ permission: Permission.PluginRead }) @Endpoint({ summary: 'List all plugins', description: 'Retrieve a list of plugins available to the authenticated user.', - history: new HistoryBuilder().added('v2.3.0').alpha('v2.3.0'), + history: HistoryBuilder.v3(), }) - getPlugins(): Promise { - return this.service.getAll(); + searchPlugins(@Query() dto: PluginSearchDto): Promise { + return this.service.search(dto); + } + + @Get('methods') + @Authenticated({ permission: Permission.PluginRead }) + @Endpoint({ + summary: 'Retrieve plugin methods', + description: 'Retrieve a list of plugin methods', + history: HistoryBuilder.v3(), + }) + searchPluginMethods(@Query() dto: PluginMethodSearchDto): Promise { + return this.service.searchMethods(dto); + } + + @Get('templates') + @Authenticated({ permission: Permission.PluginRead }) + @Endpoint({ + summary: 'Retrieve workflow templates', + description: 'Retrieve workflow templates provided by installed plugins', + history: HistoryBuilder.v3(), + }) + searchPluginTemplates(): Promise { + return this.service.searchTemplates(); } @Get(':id') @@ -39,7 +56,7 @@ export class PluginController { @Endpoint({ summary: 'Retrieve a plugin', description: 'Retrieve information about a specific plugin by its ID.', - history: new HistoryBuilder().added('v2.3.0').alpha('v2.3.0'), + history: HistoryBuilder.v3(), }) getPlugin(@Param() { id }: UUIDParamDto): Promise { return this.service.get(id); diff --git a/server/src/controllers/sync.controller.ts b/server/src/controllers/sync.controller.ts index c9f3fa7825..b696db8212 100644 --- a/server/src/controllers/sync.controller.ts +++ b/server/src/controllers/sync.controller.ts @@ -1,6 +1,6 @@ -import { Body, Controller, Delete, Get, Header, HttpCode, HttpStatus, Post, Res } from '@nestjs/common'; +import { Body, Controller, Delete, Get, Header, HttpCode, HttpStatus, Post, Req, Res } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; -import { Response } from 'express'; +import { Request, Response } from 'express'; import { Endpoint, HistoryBuilder } from 'src/decorators'; import { AuthDto } from 'src/dtos/auth.dto'; import { SyncAckDeleteDto, SyncAckDto, SyncAckSetDto, SyncStreamDto } from 'src/dtos/sync.dto'; @@ -27,12 +27,12 @@ export class SyncController { 'Retrieve a JSON lines streamed response of changes for synchronization. This endpoint is used by the mobile app to efficiently stay up to date with changes.', history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), }) - async getSyncStream(@Auth() auth: AuthDto, @Res() res: Response, @Body() dto: SyncStreamDto) { + async getSyncStream(@Auth() auth: AuthDto, @Req() req: Request, @Res() res: Response, @Body() dto: SyncStreamDto) { try { await this.service.stream(auth, res, dto); } catch (error: Error | any) { res.setHeader('Content-Type', 'application/json'); - this.errorService.handleError(res, error); + this.errorService.handleError(req, res, error); } } diff --git a/server/src/controllers/tag.controller.spec.ts b/server/src/controllers/tag.controller.spec.ts index 907e99bb43..c2a2de95bd 100644 --- a/server/src/controllers/tag.controller.spec.ts +++ b/server/src/controllers/tag.controller.spec.ts @@ -63,11 +63,5 @@ describe(TagController.name, () => { await request(ctx.getHttpServer()).put(`/tags/${factory.uuid()}`); expect(ctx.authenticate).toHaveBeenCalled(); }); - - it('should allow setting a null color via an empty string', async () => { - const id = factory.uuid(); - await request(ctx.getHttpServer()).put(`/tags/${id}`).send({ color: '' }); - expect(service.update).toHaveBeenCalledWith(undefined, id, expect.objectContaining({ color: null })); - }); }); }); diff --git a/server/src/controllers/video-stream.controller.ts b/server/src/controllers/video-stream.controller.ts new file mode 100644 index 0000000000..8707584361 --- /dev/null +++ b/server/src/controllers/video-stream.controller.ts @@ -0,0 +1,79 @@ +import { Controller, Delete, Get, Header, HttpCode, HttpStatus, Next, Param, Res } from '@nestjs/common'; +import { ApiProduces, ApiTags } from '@nestjs/swagger'; +import { NextFunction, Response } from 'express'; +import { HLS_PLAYLIST_CONTENT_TYPE } from 'src/constants'; +import { Endpoint, HistoryBuilder } from 'src/decorators'; +import { AuthDto } from 'src/dtos/auth.dto'; +import { HlsSegmentParamDto, HlsSessionParamDto, HlsVariantParamDto } from 'src/dtos/streaming.dto'; +import { ApiTag, Permission, RouteKey } from 'src/enum'; +import { Auth, Authenticated, FileResponse } from 'src/middleware/auth.guard'; +import { LoggingRepository } from 'src/repositories/logging.repository'; +import { HlsService } from 'src/services/hls.service'; +import { sendFile } from 'src/utils/file'; +import { UUIDParamDto } from 'src/validation'; + +@ApiTags(ApiTag.Assets) +@Controller(RouteKey.Asset) +export class VideoStreamController { + constructor( + private logger: LoggingRepository, + private service: HlsService, + ) {} + + @Get(':id/video/stream/main.m3u8') + @Authenticated({ permission: Permission.AssetView, sharedLink: true }) + @Header('Cache-Control', 'no-cache') + @Header('Content-Type', HLS_PLAYLIST_CONTENT_TYPE) + @ApiProduces(HLS_PLAYLIST_CONTENT_TYPE) + @Endpoint({ + summary: 'Get HLS main playlist', + description: 'Returns an HLS main playlist with all available variants for the asset.', + history: new HistoryBuilder().added('v3').alpha('v3'), + }) + getMainPlaylist(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto) { + return this.service.getMainPlaylist(auth, id); + } + + @Get(':id/video/stream/:sessionId/:variantIndex/playlist.m3u8') + @Authenticated({ permission: Permission.AssetView, sharedLink: true }) + @Header('Cache-Control', 'no-cache') + @Header('Content-Type', HLS_PLAYLIST_CONTENT_TYPE) + @ApiProduces(HLS_PLAYLIST_CONTENT_TYPE) + @Endpoint({ + summary: 'Get HLS media playlist', + description: 'Returns an HLS media playlist for one variant of the streaming session.', + history: new HistoryBuilder().added('v3').alpha('v3'), + }) + getMediaPlaylist(@Auth() auth: AuthDto, @Param() { id, sessionId }: HlsVariantParamDto) { + return this.service.getMediaPlaylist(auth, id, sessionId); + } + + @Get(':id/video/stream/:sessionId/:variantIndex/:filename') + @FileResponse() + @Authenticated({ permission: Permission.AssetView, sharedLink: true }) + @Endpoint({ + summary: 'Get HLS segment or init file', + description: 'Streams an HLS init segment (init.mp4) or media segment (seg_N.m4s).', + history: new HistoryBuilder().added('v3').alpha('v3'), + }) + async getSegment( + @Auth() auth: AuthDto, + @Param() { id, sessionId, variantIndex, filename }: HlsSegmentParamDto, + @Res() res: Response, + @Next() next: NextFunction, + ) { + await sendFile(res, next, () => this.service.getSegment(auth, id, sessionId, variantIndex, filename), this.logger); + } + + @Delete(':id/video/stream/:sessionId') + @HttpCode(HttpStatus.NO_CONTENT) + @Authenticated({ permission: Permission.AssetView, sharedLink: true }) + @Endpoint({ + summary: 'End HLS streaming session', + description: 'Releases server resources for the streaming session.', + history: new HistoryBuilder().added('v3').alpha('v3'), + }) + async endSession(@Auth() auth: AuthDto, @Param() { id, sessionId }: HlsSessionParamDto) { + await this.service.endSession(auth, id, sessionId); + } +} diff --git a/server/src/controllers/workflow.controller.spec.ts b/server/src/controllers/workflow.controller.spec.ts new file mode 100644 index 0000000000..140fb00e95 --- /dev/null +++ b/server/src/controllers/workflow.controller.spec.ts @@ -0,0 +1,113 @@ +import { WorkflowTrigger } from '@immich/plugin-sdk'; +import { WorkflowController } from 'src/controllers/workflow.controller'; +import { LoggingRepository } from 'src/repositories/logging.repository'; +import { WorkflowService } from 'src/services/workflow.service'; +import request from 'supertest'; +import { errorDto } from 'test/medium/responses'; +import { factory } from 'test/small.factory'; +import { automock, ControllerContext, controllerSetup, mockBaseService } from 'test/utils'; + +describe(WorkflowController.name, () => { + let ctx: ControllerContext; + const service = mockBaseService(WorkflowService); + + beforeAll(async () => { + ctx = await controllerSetup(WorkflowController, [ + { provide: WorkflowService, useValue: service }, + { provide: LoggingRepository, useValue: automock(LoggingRepository, { strict: false }) }, + ]); + return () => ctx.close(); + }); + + beforeEach(() => { + service.resetAllMocks(); + ctx.reset(); + }); + + describe('POST /workflows', () => { + it('should be an authenticated route', async () => { + await request(ctx.getHttpServer()).post('/workflows').send({}); + expect(ctx.authenticate).toHaveBeenCalled(); + }); + + it(`should require a valid trigger`, async () => { + const { status, body } = await request(ctx.getHttpServer()) + .post(`/workflows`) + .send({ trigger: 'invalid' }) + .set('Authorization', `Bearer token`); + expect(status).toBe(400); + expect(body).toEqual( + errorDto.validationError([ + { path: ['trigger'], message: expect.stringContaining('Invalid option: expected one of') }, + ]), + ); + }); + + it(`should require a valid enabled value`, async () => { + const { status, body } = await request(ctx.getHttpServer()) + .post(`/workflows`) + .send({ enabled: 'invalid' }) + .set('Authorization', `Bearer token`); + expect(status).toBe(400); + expect(body).toEqual( + errorDto.validationError([{ path: ['enabled'], message: 'Invalid input: expected boolean, received string' }]), + ); + }); + + it(`should not require a name`, async () => { + const { status } = await request(ctx.getHttpServer()) + .post(`/workflows`) + .send({ trigger: WorkflowTrigger.AssetCreate }) + .set('Authorization', `Bearer token`); + expect(status).toBe(201); + expect(service.create).toHaveBeenCalled(); + }); + }); + + describe('GET /workflows', () => { + it('should be an authenticated route', async () => { + await request(ctx.getHttpServer()).get('/workflows'); + expect(ctx.authenticate).toHaveBeenCalled(); + }); + + it(`should require id to be a uuid`, async () => { + const { status, body } = await request(ctx.getHttpServer()) + .get(`/workflows`) + .query({ id: 'invalid' }) + .set('Authorization', `Bearer token`); + expect(status).toBe(400); + expect(body).toEqual(errorDto.validationError([{ path: ['id'], message: 'Invalid UUID' }])); + }); + }); + + describe('GET /workflows/:id', () => { + it('should be an authenticated route', async () => { + await request(ctx.getHttpServer()).get(`/workflows/${factory.uuid()}`); + expect(ctx.authenticate).toHaveBeenCalled(); + }); + + it(`should require id to be a uuid`, async () => { + const { status, body } = await request(ctx.getHttpServer()) + .get(`/workflows/invalid`) + .set('Authorization', `Bearer token`); + expect(status).toBe(400); + expect(body).toEqual(errorDto.validationError([{ path: ['id'], message: 'Invalid UUID' }])); + }); + }); + + describe('PUT /workflows/:id', () => { + it('should be an authenticated route', async () => { + await request(ctx.getHttpServer()).put(`/workflows/${factory.uuid()}`).send({}); + expect(ctx.authenticate).toHaveBeenCalled(); + }); + + it(`should require id to be a uuid`, async () => { + const { status, body } = await request(ctx.getHttpServer()) + .put(`/workflows/invalid`) + .set('Authorization', `Bearer token`) + .send({}); + expect(status).toBe(400); + expect(body).toEqual(errorDto.validationError([{ path: ['id'], message: 'Invalid UUID' }])); + }); + }); +}); diff --git a/server/src/controllers/workflow.controller.ts b/server/src/controllers/workflow.controller.ts index e07b6443f4..59fa2eee27 100644 --- a/server/src/controllers/workflow.controller.ts +++ b/server/src/controllers/workflow.controller.ts @@ -1,8 +1,15 @@ -import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put } from '@nestjs/common'; +import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put, Query } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { Endpoint, HistoryBuilder } from 'src/decorators'; import { AuthDto } from 'src/dtos/auth.dto'; -import { WorkflowCreateDto, WorkflowResponseDto, WorkflowUpdateDto } from 'src/dtos/workflow.dto'; +import { + WorkflowCreateDto, + WorkflowResponseDto, + WorkflowSearchDto, + WorkflowShareResponseDto, + WorkflowTriggerResponseDto, + WorkflowUpdateDto, +} from 'src/dtos/workflow.dto'; import { Permission } from 'src/enum'; import { Auth, Authenticated } from 'src/middleware/auth.guard'; import { WorkflowService } from 'src/services/workflow.service'; @@ -18,7 +25,7 @@ export class WorkflowController { @Endpoint({ summary: 'Create a workflow', description: 'Create a new workflow, the workflow can also be created with empty filters and actions.', - history: new HistoryBuilder().added('v2.3.0').alpha('v2.3.0'), + history: HistoryBuilder.v3(), }) createWorkflow(@Auth() auth: AuthDto, @Body() dto: WorkflowCreateDto): Promise { return this.service.create(auth, dto); @@ -29,10 +36,21 @@ export class WorkflowController { @Endpoint({ summary: 'List all workflows', description: 'Retrieve a list of workflows available to the authenticated user.', - history: new HistoryBuilder().added('v2.3.0').alpha('v2.3.0'), + history: HistoryBuilder.v3(), }) - getWorkflows(@Auth() auth: AuthDto): Promise { - return this.service.getAll(auth); + searchWorkflows(@Auth() auth: AuthDto, @Query() dto: WorkflowSearchDto): Promise { + return this.service.search(auth, dto); + } + + @Get('triggers') + @Authenticated({ permission: false }) + @Endpoint({ + summary: 'List all workflow triggers', + description: 'Retrieve a list of all available workflow triggers.', + history: HistoryBuilder.v3(), + }) + getWorkflowTriggers(): WorkflowTriggerResponseDto[] { + return this.service.getTriggers(); } @Get(':id') @@ -40,19 +58,30 @@ export class WorkflowController { @Endpoint({ summary: 'Retrieve a workflow', description: 'Retrieve information about a specific workflow by its ID.', - history: new HistoryBuilder().added('v2.3.0').alpha('v2.3.0'), + history: HistoryBuilder.v3(), }) getWorkflow(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { return this.service.get(auth, id); } + @Get(':id/share') + @Authenticated({ permission: Permission.WorkflowRead }) + @Endpoint({ + summary: 'Retrieve a workflow', + description: 'Retrieve a workflow details without ids, default values, etc.', + history: HistoryBuilder.v3(), + }) + getWorkflowForShare(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { + return this.service.share(auth, id); + } + @Put(':id') @Authenticated({ permission: Permission.WorkflowUpdate }) @Endpoint({ summary: 'Update a workflow', description: 'Update the information of a specific workflow by its ID. This endpoint can be used to update the workflow name, description, trigger type, filters and actions order, etc.', - history: new HistoryBuilder().added('v2.3.0').alpha('v2.3.0'), + history: HistoryBuilder.v3(), }) updateWorkflow( @Auth() auth: AuthDto, @@ -68,7 +97,7 @@ export class WorkflowController { @Endpoint({ summary: 'Delete a workflow', description: 'Delete a workflow by its ID.', - history: new HistoryBuilder().added('v2.3.0').alpha('v2.3.0'), + history: HistoryBuilder.v3(), }) deleteWorkflow(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { return this.service.delete(auth, id); diff --git a/server/src/cores/storage.core.ts b/server/src/cores/storage.core.ts index 3345f6e129..8802145020 100644 --- a/server/src/cores/storage.core.ts +++ b/server/src/cores/storage.core.ts @@ -18,6 +18,7 @@ import { MoveRepository } from 'src/repositories/move.repository'; import { PersonRepository } from 'src/repositories/person.repository'; import { StorageRepository } from 'src/repositories/storage.repository'; import { SystemMetadataRepository } from 'src/repositories/system-metadata.repository'; +import { VideoInterfaces } from 'src/types'; import { getAssetFile } from 'src/utils/asset.util'; import { getConfig } from 'src/utils/config'; @@ -34,6 +35,10 @@ export interface MoveRequest { export type ThumbnailPathEntity = { id: string; ownerId: string }; +export type HlsSessionFolder = { ownerId: string; sessionId: string }; + +export type HlsVariantFolder = { ownerId: string; sessionId: string; variantIndex: number }; + export type ImagePathOptions = { fileType: AssetFileType; format: ImageFormat | RawExtractedFormat; isEdited: boolean }; let instance: StorageCore | null; @@ -124,6 +129,14 @@ export class StorageCore { return StorageCore.getNestedPath(StorageFolder.EncodedVideo, asset.ownerId, `${asset.id}.mp4`); } + static getHlsSessionFolder({ ownerId, sessionId }: HlsSessionFolder) { + return StorageCore.getNestedPath(StorageFolder.EncodedVideo, ownerId, sessionId); + } + + static getHlsVariantFolder({ ownerId, sessionId, variantIndex }: HlsVariantFolder) { + return join(StorageCore.getHlsSessionFolder({ ownerId, sessionId }), variantIndex.toString()); + } + static getAndroidMotionPath(asset: ThumbnailPathEntity, uuid: string) { return StorageCore.getNestedPath(StorageFolder.EncodedVideo, asset.ownerId, `${uuid}-MP.mp4`); } @@ -299,6 +312,11 @@ export class StorageCore { return this.storageRepository.removeEmptyDirs(StorageCore.getBaseFolder(folder)); } + async getVideoInterfaces(): Promise { + const [dri, mali] = await Promise.all([this.getDevices(), this.hasMaliOpenCL()]); + return { dri, mali }; + } + private savePath(pathType: PathType, id: string, newPath: string) { switch (pathType) { case AssetPathType.Original: { @@ -330,4 +348,26 @@ export class StorageCore { static getTempPathInDir(dir: string): string { return join(dir, `${randomUUID()}.tmp`); } + + private async getDevices() { + try { + return await this.storageRepository.readdir('/dev/dri'); + } catch { + this.logger.debug('No devices found in /dev/dri.'); + return []; + } + } + + private async hasMaliOpenCL() { + try { + const [maliIcdStat, maliDeviceStat] = await Promise.all([ + this.storageRepository.stat('/etc/OpenCL/vendors/mali.icd'), + this.storageRepository.stat('/dev/mali0'), + ]); + return maliIcdStat.isFile() && maliDeviceStat.isCharacterDevice(); + } catch { + this.logger.debug('OpenCL not available for transcoding, so RKMPP acceleration will use CPU tonemapping'); + return false; + } + } } diff --git a/server/src/database.ts b/server/src/database.ts index cbcad41f58..e9ca494b27 100644 --- a/server/src/database.ts +++ b/server/src/database.ts @@ -8,8 +8,6 @@ import { ChecksumAlgorithm, MemoryType, Permission, - PluginContext, - PluginTriggerType, SharedLinkType, SourceType, UserAvatarColor, @@ -18,10 +16,8 @@ import { import { AlbumTable } from 'src/schema/tables/album.table'; import { AssetExifTable } from 'src/schema/tables/asset-exif.table'; import { AssetTable } from 'src/schema/tables/asset.table'; -import { PluginActionTable, PluginFilterTable } from 'src/schema/tables/plugin.table'; -import { WorkflowActionTable, WorkflowFilterTable, WorkflowTable } from 'src/schema/tables/workflow.table'; +import { PluginTable } from 'src/schema/tables/plugin.table'; import { UserMetadataItem } from 'src/types'; -import type { ActionConfig, FilterConfig, JSONSchema } from 'src/types/plugin-schema.types'; export type AuthUser = { id: string; @@ -276,42 +272,7 @@ export type AssetFace = { isVisible: boolean; }; -export type PluginFilter = Selectable & { - methodName: string; - title: string; - description: string; - supportedContexts: PluginContext[]; - schema: JSONSchema | null; -}; - -export type PluginAction = Selectable & { - methodName: string; - title: string; - description: string; - supportedContexts: PluginContext[]; - schema: JSONSchema | null; -}; - -export type Workflow = Selectable & { - triggerType: PluginTriggerType; - name: string | null; - description: string; - enabled: boolean; -}; - -export type WorkflowFilter = Selectable & { - workflowId: string; - pluginFilterId: string; - filterConfig: FilterConfig | null; - order: number; -}; - -export type WorkflowAction = Selectable & { - workflowId: string; - pluginActionId: string; - actionConfig: ActionConfig | null; - order: number; -}; +export type Plugin = Selectable; const userColumns = ['id', 'name', 'email', 'avatarColor', 'profileImagePath', 'profileChangedAt'] as const; const userWithPrefixColumns = [ @@ -343,6 +304,32 @@ export const columns = { 'asset.height', 'asset.isEdited', ], + workflowAssetV1: [ + 'asset.id', + 'asset.ownerId', + 'asset.stackId', + 'asset.livePhotoVideoId', + 'asset.libraryId', + 'asset.duplicateId', + 'asset.createdAt', + 'asset.updatedAt', + 'asset.deletedAt', + 'asset.fileCreatedAt', + 'asset.fileModifiedAt', + 'asset.localDateTime', + 'asset.type', + 'asset.status', + 'asset.visibility', + 'asset.duration', + 'asset.checksum', + 'asset.originalPath', + 'asset.originalFileName', + 'asset.isOffline', + 'asset.isFavorite', + 'asset.isExternal', + 'asset.isEdited', + 'asset.isFavorite', + ], assetFiles: ['asset_file.id', 'asset_file.path', 'asset_file.type', 'asset_file.isEdited'], assetFilesForThumbnail: [ 'asset_file.id', @@ -374,6 +361,15 @@ export const columns = { tag: ['tag.id', 'tag.value', 'tag.createdAt', 'tag.updatedAt', 'tag.color', 'tag.parentId'], apiKey: ['id', 'name', 'userId', 'createdAt', 'updatedAt', 'permissions'], notification: ['id', 'createdAt', 'level', 'type', 'title', 'description', 'data', 'readAt'], + pluginMethod: [ + 'plugin_method.name', + 'plugin_method.title', + 'plugin_method.description', + 'plugin_method.types', + 'plugin_method.schema', + 'plugin_method.hostFunctions', + 'plugin_method.uiHints', + ], syncAsset: [ 'asset.id', 'asset.ownerId', @@ -382,6 +378,7 @@ export const columns = { 'asset.checksum', 'asset.fileCreatedAt', 'asset.fileModifiedAt', + 'asset.createdAt', 'asset.localDateTime', 'asset.type', 'asset.deletedAt', @@ -395,6 +392,27 @@ export const columns = { 'asset.height', 'asset.isEdited', ], + syncPartnerAsset: [ + 'asset.id', + 'asset.ownerId', + 'asset.originalFileName', + 'asset.thumbhash', + 'asset.checksum', + 'asset.fileCreatedAt', + 'asset.fileModifiedAt', + 'asset.localDateTime', + 'asset.createdAt', + 'asset.type', + 'asset.deletedAt', + 'asset.visibility', + 'asset.duration', + 'asset.livePhotoVideoId', + 'asset.stackId', + 'asset.libraryId', + 'asset.width', + 'asset.height', + 'asset.isEdited', + ], syncAlbumUser: ['album_user.albumId as albumId', 'album_user.userId as userId', 'album_user.role'], syncStack: ['stack.id', 'stack.createdAt', 'stack.updatedAt', 'stack.primaryAssetId', 'stack.ownerId'], syncUser: ['id', 'name', 'email', 'avatarColor', 'deletedAt', 'updateId', 'profileImagePath', 'profileChangedAt'], @@ -482,17 +500,6 @@ export const columns = { 'asset_exif.tags', 'asset_exif.timeZone', ], - plugin: [ - 'plugin.id as id', - 'plugin.name as name', - 'plugin.title as title', - 'plugin.description as description', - 'plugin.author as author', - 'plugin.version as version', - 'plugin.wasmPath as wasmPath', - 'plugin.createdAt as createdAt', - 'plugin.updatedAt as updatedAt', - ], } as const; export type LockableProperty = (typeof lockableProperties)[number]; diff --git a/server/src/decorators.ts b/server/src/decorators.ts index 75a3463ee7..513ae36c9f 100644 --- a/server/src/decorators.ts +++ b/server/src/decorators.ts @@ -200,6 +200,10 @@ export class HistoryBuilder { private hasDeprecated = false; private items: HistoryEntry[] = []; + static v3() { + return new HistoryBuilder().added('v3.0.0'); + } + added(version: string, description?: string) { return this.push({ version, state: 'Added', description }); } @@ -261,3 +265,13 @@ export class HistoryBuilder { return this; } } + +// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type +export const extraModels: Function[] = []; + +export const ExtraModel = (): ClassDecorator => { + // eslint-disable-next-line unicorn/consistent-function-scoping, @typescript-eslint/no-unsafe-function-type + return (object: Function) => { + extraModels.push(object); + }; +}; diff --git a/server/src/dtos/activity.dto.ts b/server/src/dtos/activity.dto.ts index 7b8ba34c91..facd4ed256 100644 --- a/server/src/dtos/activity.dto.ts +++ b/server/src/dtos/activity.dto.ts @@ -36,18 +36,16 @@ const ActivityStatisticsResponseSchema = z }) .meta({ id: 'ActivityStatisticsResponseDto' }); -const ActivitySchema = z - .object({ - albumId: z.uuidv4().describe('Album ID'), - assetId: z.uuidv4().optional().describe('Asset ID (if activity is for an asset)'), - }) - .describe('Activity'); +const ActivitySchema = z.object({ + albumId: z.uuidv4().describe('Album ID'), + assetId: z.uuidv4().optional().describe('Asset ID (if activity is for an asset)'), +}); const ActivitySearchSchema = ActivitySchema.extend({ type: ReactionTypeSchema.optional(), level: ReactionLevelSchema.optional(), userId: z.uuidv4().optional().describe('Filter by user ID'), -}).describe('Activity search'); +}); const ActivityCreateSchema = ActivitySchema.extend({ type: ReactionTypeSchema, diff --git a/server/src/dtos/album.dto.ts b/server/src/dtos/album.dto.ts index 33870cd6fc..ee6f2c07eb 100644 --- a/server/src/dtos/album.dto.ts +++ b/server/src/dtos/album.dto.ts @@ -6,7 +6,7 @@ import { MapAsset } from 'src/dtos/asset-response.dto'; import { UserResponseSchema, mapUser } from 'src/dtos/user.dto'; import { AlbumUserRole, AlbumUserRoleSchema, AssetOrder, AssetOrderSchema } from 'src/enum'; import { MaybeDehydrated } from 'src/types'; -import { asDateString } from 'src/utils/date'; +import { asDateTimeString } from 'src/utils/date'; import { stringToBool } from 'src/validation'; import z from 'zod'; @@ -65,10 +65,15 @@ const UpdateAlbumSchema = z const GetAlbumsSchema = z .object({ - shared: stringToBool + id: z.uuidv4().optional().describe('Album ID'), + name: z.string().optional().describe('Album name (exact match)'), + isOwned: stringToBool .optional() - .describe('Filter by shared status: true = only shared, false = not shared, undefined = all owned albums'), - assetId: z.uuidv4().optional().describe('Filter albums containing this asset ID (ignores shared parameter)'), + .describe('Filter by ownership: true = only owned, false = only shared-with-me, undefined = no filter'), + isShared: stringToBool + .optional() + .describe('Filter by shared status: true = only shared, false = not shared, undefined = no filter'), + assetId: z.uuidv4().optional().describe('Filter albums containing this asset ID (ignores other parameters)'), }) .meta({ id: 'GetAlbumsDto' }); @@ -190,14 +195,14 @@ export const mapAlbum = (entity: MaybeDehydrated): AlbumResponseDto albumName: entity.albumName, description: entity.description, albumThumbnailAssetId: entity.albumThumbnailAssetId, - createdAt: asDateString(entity.createdAt), - updatedAt: asDateString(entity.updatedAt), + createdAt: asDateTimeString(entity.createdAt), + updatedAt: asDateTimeString(entity.updatedAt), id: entity.id, albumUsers, shared: hasSharedUser || hasSharedLink, hasSharedLink, - startDate: asDateString(startDate), - endDate: asDateString(endDate), + startDate: asDateTimeString(startDate), + endDate: asDateTimeString(endDate), assetCount: entity.assets?.length || 0, isActivityEnabled: entity.isActivityEnabled, order: entity.order, diff --git a/server/src/dtos/asset-media.dto.ts b/server/src/dtos/asset-media.dto.ts index 8515ecc0b3..596273eddb 100644 --- a/server/src/dtos/asset-media.dto.ts +++ b/server/src/dtos/asset-media.dto.ts @@ -38,7 +38,7 @@ export enum UploadFieldName { const AssetMediaBaseSchema = z.object({ fileCreatedAt: isoDatetimeToDate.describe('File creation date'), fileModifiedAt: isoDatetimeToDate.describe('File modification date'), - duration: z.int32().min(0).optional().describe('Duration in milliseconds (for videos)'), + duration: z.coerce.number().int().min(0).optional().describe('Duration in milliseconds (for videos)'), filename: z.string().optional().describe('Filename'), /** The properties below are added to correctly generate the API docs and client SDKs. Validation should be handled in the controller. */ [UploadFieldName.ASSET_DATA]: z.any().describe('Asset file data').meta({ type: 'string', format: 'binary' }), diff --git a/server/src/dtos/asset-response.dto.spec.ts b/server/src/dtos/asset-response.dto.spec.ts deleted file mode 100644 index 8e85b983c3..0000000000 --- a/server/src/dtos/asset-response.dto.spec.ts +++ /dev/null @@ -1,192 +0,0 @@ -import { mapAsset } from 'src/dtos/asset-response.dto'; -import { AssetEditAction } from 'src/dtos/editing.dto'; -import { AssetFaceFactory } from 'test/factories/asset-face.factory'; -import { AssetFactory } from 'test/factories/asset.factory'; -import { PersonFactory } from 'test/factories/person.factory'; -import { getForAsset } from 'test/mappers'; - -describe('mapAsset', () => { - describe('peopleWithFaces', () => { - it('should transform all faces when a person has multiple faces in the same image', () => { - const person = PersonFactory.create(); - const face1 = { - boundingBoxX1: 100, - boundingBoxY1: 100, - boundingBoxX2: 200, - boundingBoxY2: 200, - imageWidth: 1000, - imageHeight: 800, - }; - - const face2 = { - boundingBoxX1: 300, - boundingBoxY1: 400, - boundingBoxX2: 400, - boundingBoxY2: 500, - imageWidth: 1000, - imageHeight: 800, - }; - - const asset = AssetFactory.from() - .face(face1, (builder) => builder.person(person)) - .face(face2, (builder) => builder.person(person)) - .exif({ exifImageWidth: 1000, exifImageHeight: 800 }) - .edit({ - action: AssetEditAction.Crop, - parameters: { - width: 1512, - height: 1152, - x: 216, - y: 1512, - }, - }) - .build(); - - const result = mapAsset(getForAsset(asset)); - - expect(result.people).toBeDefined(); - expect(result.people).toHaveLength(1); - expect(result.people![0].faces).toHaveLength(2); - - // Verify that both faces have been transformed (bounding boxes adjusted for crop) - const firstFace = result.people![0].faces[0]; - const secondFace = result.people![0].faces[1]; - - // After crop (x: 216, y: 1512), the coordinates should be adjusted - // Faces outside the crop area will be clamped - expect(firstFace.boundingBoxX1).toBe(-116); // 100 - 216 = -116 - expect(firstFace.boundingBoxY1).toBe(-1412); // 100 - 1512 = -1412 - expect(firstFace.boundingBoxX2).toBe(-16); // 200 - 216 = -16 - expect(firstFace.boundingBoxY2).toBe(-1312); // 200 - 1512 = -1312 - - expect(secondFace.boundingBoxX1).toBe(84); // 300 - 216 - expect(secondFace.boundingBoxY1).toBe(-1112); // 400 - 1512 = -1112 - expect(secondFace.boundingBoxX2).toBe(184); // 400 - 216 - expect(secondFace.boundingBoxY2).toBe(-1012); // 500 - 1512 = -1012 - }); - - it('should transform unassigned faces with edits and dimensions', () => { - const unassignedFace = AssetFaceFactory.create({ - boundingBoxX1: 100, - boundingBoxY1: 100, - boundingBoxX2: 200, - boundingBoxY2: 200, - imageWidth: 1000, - imageHeight: 800, - }); - - const asset = AssetFactory.from() - .face(unassignedFace) - .exif({ exifImageWidth: 1000, exifImageHeight: 800 }) - .edit({ action: AssetEditAction.Crop, parameters: { x: 50, y: 50, width: 500, height: 400 } }) - .build(); - - const result = mapAsset(getForAsset(asset)); - - expect(result.unassignedFaces).toBeDefined(); - expect(result.unassignedFaces).toHaveLength(1); - - // Verify that unassigned face has been transformed - const face = result.unassignedFaces![0]; - expect(face.boundingBoxX1).toBe(50); // 100 - 50 - expect(face.boundingBoxY1).toBe(50); // 100 - 50 - expect(face.boundingBoxX2).toBe(150); // 200 - 50 - expect(face.boundingBoxY2).toBe(150); // 200 - 50 - }); - - it('should handle multiple people each with multiple faces', () => { - const person1Face1 = { - boundingBoxX1: 100, - boundingBoxY1: 100, - boundingBoxX2: 200, - boundingBoxY2: 200, - imageWidth: 1000, - imageHeight: 800, - }; - - const person1Face2 = { - boundingBoxX1: 300, - boundingBoxY1: 300, - boundingBoxX2: 400, - boundingBoxY2: 400, - imageWidth: 1000, - imageHeight: 800, - }; - - const person2Face1 = { - boundingBoxX1: 500, - boundingBoxY1: 100, - boundingBoxX2: 600, - boundingBoxY2: 200, - imageWidth: 1000, - imageHeight: 800, - }; - - const person = PersonFactory.create({ id: 'person-1' }); - - const asset = AssetFactory.from() - .face(person1Face1, (builder) => builder.person(person)) - .face(person1Face2, (builder) => builder.person(person)) - .face(person2Face1, (builder) => builder.person({ id: 'person-2' })) - .exif({ exifImageWidth: 1000, exifImageHeight: 800 }) - .build(); - - const result = mapAsset(getForAsset(asset)); - - expect(result.people).toBeDefined(); - expect(result.people).toHaveLength(2); - - const person1 = result.people!.find((p) => p.id === 'person-1'); - const person2 = result.people!.find((p) => p.id === 'person-2'); - - expect(person1).toBeDefined(); - expect(person1!.faces).toHaveLength(2); - // No edits, so coordinates should be unchanged - expect(person1!.faces[0].boundingBoxX1).toBe(100); - expect(person1!.faces[0].boundingBoxY1).toBe(100); - expect(person1!.faces[1].boundingBoxX1).toBe(300); - expect(person1!.faces[1].boundingBoxY1).toBe(300); - - expect(person2).toBeDefined(); - expect(person2!.faces).toHaveLength(1); - expect(person2!.faces[0].boundingBoxX1).toBe(500); - expect(person2!.faces[0].boundingBoxY1).toBe(100); - }); - - it('should combine faces of the same person into a single entry', () => { - const face1 = { - boundingBoxX1: 100, - boundingBoxY1: 100, - boundingBoxX2: 200, - boundingBoxY2: 200, - imageWidth: 1000, - imageHeight: 800, - }; - - const face2 = { - boundingBoxX1: 300, - boundingBoxY1: 300, - boundingBoxX2: 400, - boundingBoxY2: 400, - imageWidth: 1000, - imageHeight: 800, - }; - - const person = PersonFactory.create(); - - const asset = AssetFactory.from() - .face(face1, (builder) => builder.person(person)) - .face(face2, (builder) => builder.person(person)) - .exif({ exifImageWidth: 1000, exifImageHeight: 800 }) - .build(); - - const result = mapAsset(getForAsset(asset)); - - expect(result.people).toBeDefined(); - expect(result.people).toHaveLength(1); - - expect(result.people![0].id).toBe(person.id); - expect(result.people![0].faces).toHaveLength(2); - }); - }); -}); diff --git a/server/src/dtos/asset-response.dto.ts b/server/src/dtos/asset-response.dto.ts index 99d1fe7e25..b5b0b04a2d 100644 --- a/server/src/dtos/asset-response.dto.ts +++ b/server/src/dtos/asset-response.dto.ts @@ -5,13 +5,7 @@ import { HistoryBuilder } from 'src/decorators'; import { AuthDto } from 'src/dtos/auth.dto'; import { AssetEditActionItem } from 'src/dtos/editing.dto'; import { ExifResponseSchema, mapExif } from 'src/dtos/exif.dto'; -import { - AssetFaceWithoutPersonResponseSchema, - PersonWithFacesResponseDto, - PersonWithFacesResponseSchema, - mapFacesWithoutPerson, - mapPerson, -} from 'src/dtos/person.dto'; +import { PersonResponseDto, PersonResponseSchema, mapPerson } from 'src/dtos/person.dto'; import { TagResponseSchema, mapTag } from 'src/dtos/tag.dto'; import { UserResponseSchema, mapUser } from 'src/dtos/user.dto'; import { @@ -22,10 +16,9 @@ import { AssetVisibilitySchema, ChecksumAlgorithm, } from 'src/enum'; -import { ImageDimensions, MaybeDehydrated } from 'src/types'; -import { getDimensions } from 'src/utils/asset.util'; +import { MaybeDehydrated } from 'src/types'; import { hexOrBufferToBase64 } from 'src/utils/bytes'; -import { asDateString } from 'src/utils/date'; +import { asDateTimeString } from 'src/utils/date'; import { mimeTypes } from 'src/utils/mime-types'; import z from 'zod'; @@ -107,8 +100,7 @@ export const AssetResponseSchema = SanitizedAssetResponseSchema.extend( visibility: AssetVisibilitySchema, exifInfo: ExifResponseSchema.optional(), tags: z.array(TagResponseSchema).optional(), - people: z.array(PersonWithFacesResponseSchema).optional(), - unassignedFaces: z.array(AssetFaceWithoutPersonResponseSchema).optional(), + people: z.array(PersonResponseSchema).optional(), checksum: z.string().describe('Base64 encoded SHA1 hash'), stack: AssetStackResponseSchema.nullish(), duplicateId: z.string().nullish().describe('Duplicate group ID'), @@ -170,33 +162,20 @@ export type AssetMapOptions = { auth?: AuthDto; }; -const peopleWithFaces = ( - faces?: MaybeDehydrated[], - edits?: AssetEditActionItem[], - assetDimensions?: ImageDimensions, -): PersonWithFacesResponseDto[] => { +const peopleFromFaces = (faces?: MaybeDehydrated[]): PersonResponseDto[] => { if (!faces) { return []; } - const peopleFaces: Map = new Map(); + const peopleMap: Map = new Map(); for (const face of faces) { - if (!face.person) { - continue; + if (face.person && !peopleMap.has(face.person.id)) { + peopleMap.set(face.person.id, mapPerson(face.person)); } - - if (!peopleFaces.has(face.person.id)) { - peopleFaces.set(face.person.id, { - ...mapPerson(face.person), - faces: [], - }); - } - const mappedFace = mapFacesWithoutPerson(face, edits, assetDimensions); - peopleFaces.get(face.person.id)!.faces.push(mappedFace); } - return [...peopleFaces.values()]; + return [...peopleMap.values()]; }; const mapStack = (entity: { stack?: Stack | null }) => { @@ -220,7 +199,7 @@ export function mapAsset(entity: MaybeDehydrated, options: AssetMapOpt type: entity.type, originalMimeType: mimeTypes.lookup(entity.originalFileName), thumbhash: entity.thumbhash ? hexOrBufferToBase64(entity.thumbhash) : null, - localDateTime: asDateString(entity.localDateTime), + localDateTime: asDateTimeString(entity.localDateTime), duration: entity.duration, livePhotoVideoId: entity.livePhotoVideoId, hasMetadata: false, @@ -230,11 +209,9 @@ export function mapAsset(entity: MaybeDehydrated, options: AssetMapOpt return sanitizedAssetResponse as AssetResponseDto; } - const assetDimensions = entity.exifInfo ? getDimensions(entity.exifInfo) : undefined; - return { id: entity.id, - createdAt: asDateString(entity.createdAt), + createdAt: asDateTimeString(entity.createdAt), ownerId: entity.ownerId, owner: entity.owner ? mapUser(entity.owner) : undefined, libraryId: entity.libraryId, @@ -243,10 +220,10 @@ export function mapAsset(entity: MaybeDehydrated, options: AssetMapOpt originalFileName: entity.originalFileName, originalMimeType: mimeTypes.lookup(entity.originalFileName), thumbhash: entity.thumbhash ? hexOrBufferToBase64(entity.thumbhash) : null, - fileCreatedAt: asDateString(entity.fileCreatedAt), - fileModifiedAt: asDateString(entity.fileModifiedAt), - localDateTime: asDateString(entity.localDateTime), - updatedAt: asDateString(entity.updatedAt), + fileCreatedAt: asDateTimeString(entity.fileCreatedAt), + fileModifiedAt: asDateTimeString(entity.fileModifiedAt), + localDateTime: asDateTimeString(entity.localDateTime), + updatedAt: asDateTimeString(entity.updatedAt), isFavorite: options.auth?.user.id === entity.ownerId && entity.isFavorite, isArchived: entity.visibility === AssetVisibility.Archive, isTrashed: !!entity.deletedAt, @@ -255,10 +232,7 @@ export function mapAsset(entity: MaybeDehydrated, options: AssetMapOpt exifInfo: entity.exifInfo ? mapExif(entity.exifInfo) : undefined, livePhotoVideoId: entity.livePhotoVideoId, tags: entity.tags?.map((tag) => mapTag(tag)), - people: peopleWithFaces(entity.faces, entity.edits, assetDimensions), - unassignedFaces: entity.faces - ?.filter((face) => !face.person) - .map((face) => mapFacesWithoutPerson(face, entity.edits, assetDimensions)), + people: peopleFromFaces(entity.faces), checksum: hexOrBufferToBase64(entity.checksum)!, stack: withStack ? mapStack(entity) : undefined, isOffline: entity.isOffline, diff --git a/server/src/dtos/exif.dto.ts b/server/src/dtos/exif.dto.ts index 37274ee1f9..d07bbcfb0e 100644 --- a/server/src/dtos/exif.dto.ts +++ b/server/src/dtos/exif.dto.ts @@ -1,7 +1,7 @@ import { createZodDto } from 'nestjs-zod'; import { Exif } from 'src/database'; import { MaybeDehydrated } from 'src/types'; -import { asDateString } from 'src/utils/date'; +import { asDateTimeString } from 'src/utils/date'; import z from 'zod'; export const ExifResponseSchema = z @@ -44,8 +44,8 @@ export function mapExif(entity: MaybeDehydrated): ExifResponseDto { exifImageHeight: entity.exifImageHeight, fileSizeInByte: entity.fileSizeInByte ? Number.parseInt(entity.fileSizeInByte.toString()) : null, orientation: entity.orientation, - dateTimeOriginal: asDateString(entity.dateTimeOriginal), - modifyDate: asDateString(entity.modifyDate), + dateTimeOriginal: asDateTimeString(entity.dateTimeOriginal), + modifyDate: asDateTimeString(entity.modifyDate), timeZone: entity.timeZone, lensModel: entity.lensModel, fNumber: entity.fNumber, diff --git a/server/src/dtos/json-schema.dto.ts b/server/src/dtos/json-schema.dto.ts new file mode 100644 index 0000000000..65b92277d5 --- /dev/null +++ b/server/src/dtos/json-schema.dto.ts @@ -0,0 +1,32 @@ +import { createZodDto } from 'nestjs-zod'; +import z from 'zod'; + +export const JsonSchemaTypeSchema = z + .enum(['string', 'number', 'integer', 'boolean', 'object']) + .meta({ id: 'JsonSchemaType' }); + +const JsonSchemaPropertySchema = z + .object({ + type: JsonSchemaTypeSchema.optional().default('object').describe('Type'), + title: z.string().describe('Title'), + description: z.string().describe('Description'), + default: z.any().optional().describe('Default value'), + enum: z.array(z.string()).optional().describe('Valid choices for enum types'), + array: z.boolean().optional().describe('Type is an array type'), + required: z.array(z.string()).optional().describe('A list of required properties'), + uiHint: z.string().optional(), + get properties() { + return z.record(z.string(), JsonSchemaPropertySchema).optional(); + }, + }) + .meta({ id: 'JsonSchemaPropertyDto' }); + +export const JsonSchemaSchema = z + .object({ + ...JsonSchemaPropertySchema.shape, + title: z.string().optional().describe('Title'), + description: z.string().optional().describe('Description'), + }) + .meta({ id: 'JsonSchemaDto' }); + +export class JsonSchemaDto extends createZodDto(JsonSchemaSchema) {} diff --git a/server/src/dtos/person.dto.ts b/server/src/dtos/person.dto.ts index 8cbbe6df78..38e856b8c1 100644 --- a/server/src/dtos/person.dto.ts +++ b/server/src/dtos/person.dto.ts @@ -7,22 +7,24 @@ import { AssetEditActionItem } from 'src/dtos/editing.dto'; import { SourceTypeSchema } from 'src/enum'; import { AssetFaceTable } from 'src/schema/tables/asset-face.table'; import { ImageDimensions, MaybeDehydrated } from 'src/types'; -import { asBirthDateString, asDateString } from 'src/utils/date'; +import { asDateString, asDateTimeString } from 'src/utils/date'; import { transformFaceBoundingBox } from 'src/utils/transform'; -import { emptyStringToNull, hexColor, stringToBool } from 'src/validation'; +import { hexColor, stringToBool } from 'src/validation'; import z from 'zod'; const PersonCreateSchema = z .object({ name: z.string().optional().describe('Person name'), - // Note: the mobile app cannot currently set the birth date to null. - birthDate: emptyStringToNull(z.string().meta({ format: 'date' }).nullable()) + birthDate: z + .string() + .meta({ format: 'date' }) + .nullable() .optional() .refine((val) => (val ? new Date(val) <= new Date() : true), { error: 'Birth date cannot be in the future' }) .describe('Person date of birth'), isHidden: z.boolean().optional().describe('Person visibility (hidden)'), isFavorite: z.boolean().optional().describe('Mark as favorite'), - color: emptyStringToNull(hexColor.nullable()).optional().describe('Person color (hex)'), + color: hexColor.nullable().optional().describe('Person color (hex)'), }) .meta({ id: 'PersonCreateDto' }); @@ -56,7 +58,7 @@ const PersonSearchSchema = z }) .meta({ id: 'PersonSearchDto' }); -const PersonResponseSchema = z +export const PersonResponseSchema = z .object({ id: z.string().describe('Person ID'), name: z.string().describe('Person name'), @@ -91,7 +93,7 @@ export class MergePersonDto extends createZodDto(MergePersonSchema) {} export class PersonSearchDto extends createZodDto(PersonSearchSchema) {} export class PersonResponseDto extends createZodDto(PersonResponseSchema) {} -export const AssetFaceWithoutPersonResponseSchema = z +export const AssetFaceResponseSchema = z .object({ id: z.uuidv4().describe('Face ID'), imageHeight: z.int().min(0).describe('Image height in pixels'), @@ -101,21 +103,10 @@ export const AssetFaceWithoutPersonResponseSchema = z boundingBoxY1: z.int().describe('Bounding box Y1 coordinate'), boundingBoxY2: z.int().describe('Bounding box Y2 coordinate'), sourceType: SourceTypeSchema.optional(), + person: PersonResponseSchema.nullable(), }) - .describe('Asset face without person') - .meta({ id: 'AssetFaceWithoutPersonResponseDto' }); - -class AssetFaceWithoutPersonResponseDto extends createZodDto(AssetFaceWithoutPersonResponseSchema) {} - -export const PersonWithFacesResponseSchema = PersonResponseSchema.extend({ - faces: z.array(AssetFaceWithoutPersonResponseSchema), -}).meta({ id: 'PersonWithFacesResponseDto' }); - -export class PersonWithFacesResponseDto extends createZodDto(PersonWithFacesResponseSchema) {} - -const AssetFaceResponseSchema = AssetFaceWithoutPersonResponseSchema.extend({ - person: PersonResponseSchema.nullable(), -}).meta({ id: 'AssetFaceResponseDto' }); + .describe('Asset face with person') + .meta({ id: 'AssetFaceResponseDto' }); export class AssetFaceResponseDto extends createZodDto(AssetFaceResponseSchema) {} @@ -184,20 +175,20 @@ export function mapPerson(person: MaybeDehydrated): PersonResponseDto { return { id: person.id, name: person.name, - birthDate: asBirthDateString(person.birthDate), + birthDate: asDateString(person.birthDate), thumbnailPath: person.thumbnailPath, isHidden: person.isHidden, isFavorite: person.isFavorite, color: person.color ?? undefined, - updatedAt: asDateString(person.updatedAt), + updatedAt: asDateTimeString(person.updatedAt), }; } -export function mapFacesWithoutPerson( +function mapFacesWithoutPerson( face: MaybeDehydrated>, edits?: AssetEditActionItem[], assetDimensions?: ImageDimensions, -): AssetFaceWithoutPersonResponseDto { +) { return { id: face.id, ...transformFaceBoundingBox( diff --git a/server/src/dtos/plugin-manifest.dto.ts b/server/src/dtos/plugin-manifest.dto.ts index 30aa8c0a68..b175c6e1bb 100644 --- a/server/src/dtos/plugin-manifest.dto.ts +++ b/server/src/dtos/plugin-manifest.dto.ts @@ -1,39 +1,48 @@ import { createZodDto } from 'nestjs-zod'; -import { PluginContextSchema } from 'src/enum'; -import { JSONSchemaSchema } from 'src/types/plugin-schema.types'; +import { JsonSchemaSchema } from 'src/dtos/json-schema.dto'; +import { WorkflowTriggerSchema, WorkflowTypeSchema } from 'src/enum'; import z from 'zod'; const pluginNameRegex = /^[a-z0-9-]+[a-z0-9]$/; const semverRegex = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/; -const PluginManifestWasmSchema = z - .object({ - path: z.string().describe('WASM file path'), - }) - .meta({ id: 'PluginManifestWasmDto' }); +export const PluginManifestMethodSchemaSchema = JsonSchemaSchema.nullable() + .optional() + .transform((value) => (value && Object.keys(value).length === 0 ? null : value)); -const PluginManifestFilterSchema = z +const PluginManifestMethodSchema = z .object({ - methodName: z.string().describe('Filter method name'), - title: z.string().describe('Filter title'), - description: z.string().describe('Filter description'), - supportedContexts: z.array(PluginContextSchema).min(1).describe('Supported contexts'), - schema: JSONSchemaSchema.optional(), + name: z.string().min(1).describe('Method name'), + title: z.string().min(1).describe('Method title'), + description: z.string().min(1).describe('Method description'), + types: z.array(WorkflowTypeSchema).min(1).describe('Workflow type'), + hostFunctions: z.boolean().optional().default(false).describe('Method uses host functions'), + schema: PluginManifestMethodSchemaSchema.describe('Schema'), + uiHints: z.array(z.string()).optional().describe('Ui hints, for example "filter"'), }) - .meta({ id: 'PluginManifestFilterDto' }); + .meta({ id: 'PluginManifestMethodDto' }); -const PluginManifestActionSchema = z +const PluginManifestTemplateStepSchema = z .object({ - methodName: z.string().describe('Action method name'), - title: z.string().describe('Action title'), - description: z.string().describe('Action description'), - supportedContexts: z.array(PluginContextSchema).min(1).describe('Supported contexts'), - schema: JSONSchemaSchema.optional(), + method: z.string().min(1).describe('Step plugin method (pluginName#methodName)'), + config: z.record(z.string(), z.unknown()).nullable().optional().describe('Step configuration'), + enabled: z.boolean().optional().describe('Whether the step is enabled'), }) - .meta({ id: 'PluginManifestActionDto' }); + .meta({ id: 'PluginManifestTemplateStepDto' }); -export const PluginManifestSchema = z +const PluginManifestTemplateSchema = z + .object({ + name: z.string().min(1).describe('Template name (must be unique within the manifest)'), + title: z.string().min(1).describe('Template title'), + description: z.string().min(1).describe('Template description'), + trigger: WorkflowTriggerSchema.describe('Workflow trigger'), + steps: z.array(PluginManifestTemplateStepSchema).describe('Workflow steps'), + uiHints: z.array(z.string()).optional().default([]).describe('Ui hints, for example "smart-album"'), + }) + .meta({ id: 'PluginManifestTemplateDto' }); + +const PluginManifestSchema = z .object({ name: z .string() @@ -44,12 +53,19 @@ export const PluginManifestSchema = z ) .describe('Plugin name (lowercase, numbers, hyphens only)'), version: z.string().regex(semverRegex).describe('Plugin version (semver)'), - title: z.string().describe('Plugin title'), - description: z.string().describe('Plugin description'), - author: z.string().describe('Plugin author'), - wasm: PluginManifestWasmSchema, - filters: z.array(PluginManifestFilterSchema).optional().describe('Plugin filters'), - actions: z.array(PluginManifestActionSchema).optional().describe('Plugin actions'), + title: z.string().min(1).describe('Plugin title'), + description: z.string().min(1).describe('Plugin description'), + wasmPath: z.string().min(1).describe('WASM file path'), + author: z.string().min(1).describe('Plugin author'), + methods: z.array(PluginManifestMethodSchema).optional().default([]).describe('Plugin methods'), + templates: z + .array(PluginManifestTemplateSchema) + .optional() + .default([]) + .refine((templates) => new Set(templates.map((t) => t.name)).size === templates.length, { + error: 'Template names must be unique within the manifest', + }) + .describe('Workflow templates'), }) .meta({ id: 'PluginManifestDto' }); diff --git a/server/src/dtos/plugin.dto.ts b/server/src/dtos/plugin.dto.ts index 2f928841cb..8ee695d61c 100644 --- a/server/src/dtos/plugin.dto.ts +++ b/server/src/dtos/plugin.dto.ts @@ -1,39 +1,34 @@ +import { WorkflowTrigger } from '@immich/plugin-sdk'; import { createZodDto } from 'nestjs-zod'; -import { PluginAction, PluginFilter } from 'src/database'; -import { PluginContextSchema, PluginTriggerTypeSchema } from 'src/enum'; -import { JSONSchemaSchema } from 'src/types/plugin-schema.types'; +import { JsonSchemaDto } from 'src/dtos/json-schema.dto'; +import { WorkflowTriggerSchema, WorkflowType, WorkflowTypeSchema } from 'src/enum'; +import { asPluginKey } from 'src/utils/workflow'; import z from 'zod'; -const PluginTriggerResponseSchema = z +const PluginSearchSchema = z .object({ - type: PluginTriggerTypeSchema, - contextType: PluginContextSchema, + id: z.uuidv4().optional().describe('Plugin ID'), + enabled: z.boolean().optional().describe('Whether the plugin is enabled'), + name: z.string().optional(), + version: z.string().optional(), + title: z.string().optional(), + description: z.string().optional(), }) - .meta({ id: 'PluginTriggerResponseDto' }); + .meta({ id: 'PluginSearchDto' }); -const PluginFilterResponseSchema = z +const PluginMethodResponseSchema = z .object({ - id: z.string().describe('Filter ID'), - pluginId: z.string().describe('Plugin ID'), - methodName: z.string().describe('Method name'), - title: z.string().describe('Filter title'), - description: z.string().describe('Filter description'), - supportedContexts: z.array(PluginContextSchema).describe('Supported contexts'), - schema: JSONSchemaSchema.nullable().describe('Filter schema'), + key: z.string().describe('Key'), + name: z.string().describe('Name'), + title: z.string().describe('Title'), + description: z.string().describe('Description'), + types: z.array(WorkflowTypeSchema).describe('Workflow types'), + uiHints: z.array(z.string()).describe('Ui hints'), + // TODO fix this + schema: z.object().optional(), + hostFunctions: z.boolean(), }) - .meta({ id: 'PluginFilterResponseDto' }); - -const PluginActionResponseSchema = z - .object({ - id: z.string().describe('Action ID'), - pluginId: z.string().describe('Plugin ID'), - methodName: z.string().describe('Method name'), - title: z.string().describe('Action title'), - description: z.string().describe('Action description'), - supportedContexts: z.array(PluginContextSchema).describe('Supported contexts'), - schema: JSONSchemaSchema.nullable().describe('Action schema'), - }) - .meta({ id: 'PluginActionResponseDto' }); + .meta({ id: 'PluginMethodResponseDto' }); const PluginResponseSchema = z .object({ @@ -45,29 +40,101 @@ const PluginResponseSchema = z version: z.string().describe('Plugin version'), createdAt: z.string().describe('Creation date'), updatedAt: z.string().describe('Last update date'), - filters: z.array(PluginFilterResponseSchema).describe('Plugin filters'), - actions: z.array(PluginActionResponseSchema).describe('Plugin actions'), + methods: z.array(PluginMethodResponseSchema).describe('Plugin methods'), }) .meta({ id: 'PluginResponseDto' }); -export class PluginTriggerResponseDto extends createZodDto(PluginTriggerResponseSchema) {} -export class PluginResponseDto extends createZodDto(PluginResponseSchema) {} +const PluginTemplateStepResponseSchema = z + .object({ + method: z.string().describe('Step plugin method'), + config: z.record(z.string(), z.unknown()).nullable().describe('Step configuration'), + enabled: z.boolean().optional().describe('Whether the step is enabled'), + }) + .meta({ id: 'PluginTemplateStepResponseDto' }); -type MapPlugin = { +const PluginTemplateResponseSchema = z + .object({ + key: z.string().describe('Template key (unique across all templates)'), + title: z.string().describe('Template title'), + description: z.string().describe('Template description'), + trigger: WorkflowTriggerSchema.describe('Workflow trigger'), + steps: z.array(PluginTemplateStepResponseSchema).describe('Workflow steps'), + uiHints: z.array(z.string()).describe('Ui hints, for example "smart-album"'), + }) + .meta({ id: 'PluginTemplateResponseDto' }); + +const PluginMethodSearchSchema = z + .object({ + id: z.uuidv4().optional().describe('Plugin method ID'), + enabled: z.boolean().optional().describe('Whether the plugin method is enabled'), + name: z.string().optional(), + title: z.string().optional(), + description: z.string().optional(), + type: WorkflowTypeSchema.optional().describe('Workflow types'), + trigger: WorkflowTriggerSchema.optional().describe('Workflow trigger'), + pluginName: z.string().optional().describe('Plugin name'), + pluginVersion: z.string().optional().describe('Plugin version'), + }) + .meta({ id: 'PluginMethodSearchDto' }); + +export class PluginSearchDto extends createZodDto(PluginSearchSchema) {} +export class PluginResponseDto extends createZodDto(PluginResponseSchema) {} +export class PluginMethodSearchDto extends createZodDto(PluginMethodSearchSchema) {} +export class PluginMethodResponseDto extends createZodDto(PluginMethodResponseSchema) {} +export class PluginTemplateResponseDto extends createZodDto(PluginTemplateResponseSchema) {} + +export type PluginTemplate = { + name: string; + title: string; + description: string; + trigger: WorkflowTrigger; + steps: Array<{ + method: string; + config?: Record | null; + enabled?: boolean; + }>; + uiHints: string[]; +}; + +export const mapTemplate = (plugin: { name: string }, template: PluginTemplate): PluginTemplateResponseDto => { + return { + key: asPluginKey({ pluginName: plugin.name, name: template.name }), + title: template.title, + description: template.description, + trigger: template.trigger, + steps: template.steps.map((step) => ({ + method: step.method, + config: step.config ?? null, + enabled: step.enabled, + })), + uiHints: template.uiHints ?? [], + }; +}; + +type Plugin = { id: string; name: string; title: string; description: string; author: string; version: string; - wasmPath: string; createdAt: Date; updatedAt: Date; - filters: PluginFilter[]; - actions: PluginAction[]; + methods: PluginMethod[]; }; -export function mapPlugin(plugin: MapPlugin): PluginResponseDto { +type PluginMethod = { + pluginName: string; + name: string; + title: string; + description: string; + types: WorkflowType[]; + schema: JsonSchemaDto | null; + hostFunctions: boolean; + uiHints: string[]; +}; + +export function mapPlugin(plugin: Plugin): PluginResponseDto { return { id: plugin.id, name: plugin.name, @@ -77,7 +144,19 @@ export function mapPlugin(plugin: MapPlugin): PluginResponseDto { version: plugin.version, createdAt: plugin.createdAt.toISOString(), updatedAt: plugin.updatedAt.toISOString(), - filters: plugin.filters, - actions: plugin.actions, + methods: plugin.methods.map((method) => mapMethod(method)), }; } + +export const mapMethod = (method: PluginMethod): PluginMethodResponseDto => { + return { + key: asPluginKey({ pluginName: method.pluginName, name: method.name }), + name: method.name, + title: method.title, + hostFunctions: method.hostFunctions, + uiHints: method.uiHints, + description: method.description, + types: method.types, + schema: method.schema as any, + }; +}; diff --git a/server/src/dtos/search.dto.ts b/server/src/dtos/search.dto.ts index ae5b5e2c8c..39276bffe0 100644 --- a/server/src/dtos/search.dto.ts +++ b/server/src/dtos/search.dto.ts @@ -4,7 +4,7 @@ import { HistoryBuilder } from 'src/decorators'; import { AlbumResponseSchema } from 'src/dtos/album.dto'; import { AssetResponseSchema } from 'src/dtos/asset-response.dto'; import { AssetOrder, AssetOrderSchema, AssetTypeSchema, AssetVisibilitySchema } from 'src/enum'; -import { emptyStringToNull, isoDatetimeToDate, stringToBool } from 'src/validation'; +import { isoDatetimeToDate, stringToBool } from 'src/validation'; import z from 'zod'; const BaseSearchSchema = z.object({ @@ -23,12 +23,12 @@ const BaseSearchSchema = z.object({ trashedAfter: isoDatetimeToDate.optional().describe('Filter by trash date (after)'), takenBefore: isoDatetimeToDate.optional().describe('Filter by taken date (before)'), takenAfter: isoDatetimeToDate.optional().describe('Filter by taken date (after)'), - city: emptyStringToNull(z.string().nullable()).optional().describe('Filter by city name'), - state: emptyStringToNull(z.string().nullable()).optional().describe('Filter by state/province name'), - country: emptyStringToNull(z.string().nullable()).optional().describe('Filter by country name'), - make: emptyStringToNull(z.string().nullable()).optional().describe('Filter by camera make'), - model: emptyStringToNull(z.string().nullable()).optional().describe('Filter by camera model'), - lensModel: emptyStringToNull(z.string().nullable()).optional().describe('Filter by lens model'), + city: z.string().nullable().optional().describe('Filter by city name'), + state: z.string().nullable().optional().describe('Filter by state/province name'), + country: z.string().nullable().optional().describe('Filter by country name'), + make: z.string().nullable().optional().describe('Filter by camera make'), + model: z.string().nullable().optional().describe('Filter by camera model'), + lensModel: z.string().nullable().optional().describe('Filter by lens model'), isNotInAlbum: z.boolean().optional().describe('Filter assets not in any album'), personIds: z.array(z.uuidv4()).optional().describe('Filter by person IDs'), tagIds: z.array(z.uuidv4()).nullish().describe('Filter by tag IDs'), @@ -186,7 +186,11 @@ const SearchAlbumResponseSchema = z const SearchAssetResponseSchema = z .object({ - total: z.int().min(0).describe('Total number of matching assets'), + total: z + .int() + .min(0) + .describe('Total number of matching assets') + .meta(new HistoryBuilder().deprecated('v3.0.0').getExtensions()), count: z.int().min(0).describe('Number of assets in this page'), items: z.array(AssetResponseSchema), facets: z.array(SearchFacetResponseSchema), diff --git a/server/src/dtos/server.dto.ts b/server/src/dtos/server.dto.ts index 57a58e1dd7..03d45fab1c 100644 --- a/server/src/dtos/server.dto.ts +++ b/server/src/dtos/server.dto.ts @@ -1,5 +1,6 @@ import { createZodDto } from 'nestjs-zod'; import type { SemVer } from 'semver'; +import { ExtraModel, HistoryBuilder } from 'src/decorators'; import { isoDatetimeToDate } from 'src/validation'; import z from 'zod'; @@ -58,9 +59,15 @@ const ServerStorageResponseSchema = z const ServerVersionResponseSchema = z .object({ - major: z.int().describe('Major version number'), - minor: z.int().describe('Minor version number'), - patch: z.int().describe('Patch version number'), + major: z.int().min(0).describe('Major version number'), + minor: z.int().min(0).describe('Minor version number'), + patch: z.int().min(0).describe('Patch version number'), + prerelease: z + .int() + .min(0) + .nullable() + .meta(HistoryBuilder.v3().getExtensions()) + .describe('Pre-release version number'), }) .meta({ id: 'ServerVersionResponseDto' }); @@ -117,6 +124,7 @@ const ServerConfigSchema = z mapDarkStyleUrl: z.string().describe('Map dark style URL'), mapLightStyleUrl: z.string().describe('Map light style URL'), maintenanceMode: z.boolean().describe('Whether maintenance mode is active'), + minFaces: z.int().describe('People min faces server default'), }) .meta({ id: 'ServerConfigDto' }); @@ -137,9 +145,30 @@ const ServerFeaturesSchema = z search: z.boolean().describe('Whether search is enabled'), email: z.boolean().describe('Whether email notifications are enabled'), ocr: z.boolean().describe('Whether OCR is enabled'), + realtimeTranscoding: z.boolean().describe('Whether real-time transcoding is enabled'), }) .meta({ id: 'ServerFeaturesDto' }); +export enum ReleaseType { + Major = 'major', + Premajor = 'premajor', + Minor = 'minor', + Preminor = 'preminor', + Patch = 'patch', + Prepatch = 'prepatch', + Prerelease = 'prerelease', +} + +const ReleaseTypeSchema = z.enum(ReleaseType).meta({ id: 'ReleaseType' }).describe('Release type'); + +const ReleaseEventV1Schema = z.object({ + isAvailable: z.boolean().describe('Whether a new version is available'), + checkedAt: z.string().describe('When the server last checked for a latest version. As an ISO timestamp'), + serverVersion: ServerVersionResponseSchema, + releaseVersion: ServerVersionResponseSchema, + type: ReleaseTypeSchema.nullable(), +}); + export class ServerPingResponse extends createZodDto(ServerPingResponseSchema) {} export class ServerAboutResponseDto extends createZodDto(ServerAboutResponseSchema) {} export class ServerApkLinksDto extends createZodDto(ServerApkLinksSchema) {} @@ -147,7 +176,12 @@ export class ServerStorageResponseDto extends createZodDto(ServerStorageResponse export class ServerVersionResponseDto extends createZodDto(ServerVersionResponseSchema) { static fromSemVer(value: SemVer): z.infer { - return { major: value.major, minor: value.minor, patch: value.patch }; + return { + major: value.major, + minor: value.minor, + patch: value.patch, + prerelease: (value.prerelease[1] as number) ?? null, + }; } } @@ -158,10 +192,5 @@ export class ServerMediaTypesResponseDto extends createZodDto(ServerMediaTypesRe export class ServerConfigDto extends createZodDto(ServerConfigSchema) {} export class ServerFeaturesDto extends createZodDto(ServerFeaturesSchema) {} -export interface ReleaseNotification { - isAvailable: boolean; - /** ISO8601 */ - checkedAt: string; - serverVersion: ServerVersionResponseDto; - releaseVersion: ServerVersionResponseDto; -} +@ExtraModel() +export class ReleaseEventV1 extends createZodDto(ReleaseEventV1Schema) {} diff --git a/server/src/dtos/shared-link.dto.ts b/server/src/dtos/shared-link.dto.ts index 2e466c5014..aebe6fcab1 100644 --- a/server/src/dtos/shared-link.dto.ts +++ b/server/src/dtos/shared-link.dto.ts @@ -4,7 +4,7 @@ import { HistoryBuilder } from 'src/decorators'; import { AlbumResponseSchema, mapAlbum } from 'src/dtos/album.dto'; import { AssetResponseSchema, mapAsset } from 'src/dtos/asset-response.dto'; import { SharedLinkTypeSchema } from 'src/enum'; -import { emptyStringToNull, isoDatetimeToDate } from 'src/validation'; +import { isoDatetimeToDate } from 'src/validation'; import z from 'zod'; const SharedLinkSearchSchema = z @@ -23,9 +23,9 @@ const SharedLinkCreateSchema = z type: SharedLinkTypeSchema, assetIds: z.array(z.uuidv4()).optional().describe('Asset IDs (for individual assets)'), albumId: z.uuidv4().optional().describe('Album ID (for album sharing)'), - description: emptyStringToNull(z.string().nullable()).optional().describe('Link description'), - password: emptyStringToNull(z.string().nullable()).optional().describe('Link password'), - slug: emptyStringToNull(z.string().nullable()).optional().describe('Custom URL slug'), + description: z.string().nullable().optional().describe('Link description'), + password: z.string().nullable().optional().describe('Link password'), + slug: z.string().nullable().optional().describe('Custom URL slug'), expiresAt: isoDatetimeToDate.nullable().describe('Expiration date').default(null).optional(), allowUpload: z.boolean().optional().describe('Allow uploads'), allowDownload: z.boolean().default(true).optional().describe('Allow downloads'), @@ -35,19 +35,13 @@ const SharedLinkCreateSchema = z const SharedLinkEditSchema = z .object({ - description: emptyStringToNull(z.string().nullable()).optional().describe('Link description'), - password: emptyStringToNull(z.string().nullable()).optional().describe('Link password'), - slug: emptyStringToNull(z.string().nullable()).optional().describe('Custom URL slug'), + description: z.string().nullable().optional().describe('Link description'), + password: z.string().nullable().optional().describe('Link password'), + slug: z.string().nullable().optional().describe('Custom URL slug'), expiresAt: isoDatetimeToDate.nullish().describe('Expiration date'), allowUpload: z.boolean().optional().describe('Allow uploads'), allowDownload: z.boolean().optional().describe('Allow downloads'), showMetadata: z.boolean().optional().describe('Show metadata'), - changeExpiryTime: z - .boolean() - .optional() - .describe( - 'Whether to change the expiry time. Few clients cannot send null to set the expiryTime to never. Setting this flag and not sending expiryAt is considered as null instead. Clients that can send null values can ignore this.', - ), }) .meta({ id: 'SharedLinkEditDto' }); diff --git a/server/src/dtos/streaming.dto.ts b/server/src/dtos/streaming.dto.ts new file mode 100644 index 0000000000..5270e45fc2 --- /dev/null +++ b/server/src/dtos/streaming.dto.ts @@ -0,0 +1,26 @@ +import { createZodDto } from 'nestjs-zod'; +import z from 'zod'; + +const HlsSessionParamSchema = z.object({ + id: z.uuidv4(), + sessionId: z.uuidv4(), +}); + +export class HlsSessionParamDto extends createZodDto(HlsSessionParamSchema) {} + +const HlsVariantParamSchema = z.object({ + id: z.uuidv4(), + sessionId: z.uuidv4(), + variantIndex: z.coerce.number().int().min(0), +}); + +export class HlsVariantParamDto extends createZodDto(HlsVariantParamSchema) {} + +const HlsSegmentParamSchema = z.object({ + id: z.uuidv4(), + sessionId: z.uuidv4(), + variantIndex: z.coerce.number().int().min(0), + filename: z.string().regex(/^(init\.mp4|seg_\d+\.m4s)$/, { error: 'Invalid HLS segment filename' }), +}); + +export class HlsSegmentParamDto extends createZodDto(HlsSegmentParamSchema) {} diff --git a/server/src/dtos/sync.dto.ts b/server/src/dtos/sync.dto.ts index 821e53bafc..531ac6277c 100644 --- a/server/src/dtos/sync.dto.ts +++ b/server/src/dtos/sync.dto.ts @@ -1,5 +1,5 @@ -/* eslint-disable @typescript-eslint/no-unsafe-function-type */ import { createZodDto } from 'nestjs-zod'; +import { ExtraModel } from 'src/decorators'; import { AssetEditActionSchema } from 'src/dtos/editing.dto'; import { AlbumUserRole, @@ -17,15 +17,6 @@ import { import { isoDatetimeToDate } from 'src/validation'; import z from 'zod'; -export const extraSyncModels: Function[] = []; - -const ExtraModel = (): ClassDecorator => { - // eslint-disable-next-line unicorn/consistent-function-scoping - return (object: Function) => { - extraSyncModels.push(object); - }; -}; - const SyncUserV1Schema = z .object({ id: z.string().describe('User ID'), @@ -75,6 +66,7 @@ const SyncAssetV1Schema = z checksum: z.string().describe('Checksum'), fileCreatedAt: isoDatetimeToDate.nullable().describe('File created at'), fileModifiedAt: isoDatetimeToDate.nullable().describe('File modified at'), + createdAt: isoDatetimeToDate.nullable().describe('Uploaded to Immich at'), localDateTime: isoDatetimeToDate.nullable().describe('Local date time'), duration: z.string().nullable().describe('Duration'), type: AssetTypeSchema, @@ -99,6 +91,7 @@ const SyncAssetV2Schema = z checksum: z.string().describe('Checksum'), fileCreatedAt: isoDatetimeToDate.nullable().describe('File created at'), fileModifiedAt: isoDatetimeToDate.nullable().describe('File modified at'), + createdAt: isoDatetimeToDate.nullable().describe('Uploaded to Immich at'), localDateTime: isoDatetimeToDate.nullable().describe('Local date time'), duration: z.int32().min(0).nullable().describe('Duration'), type: AssetTypeSchema, diff --git a/server/src/dtos/system-config.dto.ts b/server/src/dtos/system-config.dto.ts index 4563405093..3b31705918 100644 --- a/server/src/dtos/system-config.dto.ts +++ b/server/src/dtos/system-config.dto.ts @@ -7,7 +7,6 @@ import { OcrConfigSchema, } from 'src/dtos/model-config.dto'; import { - AudioCodec, AudioCodecSchema, ColorspaceSchema, CQModeSchema, @@ -65,10 +64,7 @@ const SystemConfigFFmpegSchema = z targetVideoCodec: VideoCodecSchema, acceptedVideoCodecs: z.array(VideoCodecSchema).describe('Accepted video codecs'), targetAudioCodec: AudioCodecSchema, - acceptedAudioCodecs: z - .array(AudioCodecSchema) - .transform((value): AudioCodec[] => value.map((v) => (v === AudioCodec.Libopus ? AudioCodec.Opus : v))) - .describe('Accepted audio codecs'), + acceptedAudioCodecs: z.array(AudioCodecSchema).describe('Accepted audio codecs'), acceptedContainers: z.array(VideoContainerSchema).describe('Accepted containers'), targetResolution: z.string().describe('Target resolution'), maxBitrate: z.string().describe('Max bitrate'), @@ -83,6 +79,11 @@ const SystemConfigFFmpegSchema = z accel: TranscodeHardwareAccelerationSchema, accelDecode: configBool.describe('Accelerated decode'), tonemap: ToneMappingSchema, + realtime: z + .object({ + enabled: configBool.describe('Enable real-time HLS transcoding (alpha)'), + }) + .meta({ id: 'SystemConfigFFmpegRealtimeDto' }), }) .meta({ id: 'SystemConfigFFmpegDto' }); @@ -155,8 +156,15 @@ const SystemConfigMapSchema = z }) .meta({ id: 'SystemConfigMapDto' }); +export enum ReleaseChannel { + Stable = 'stable', + ReleaseCandidate = 'releaseCandidate', +} + +const ReleaseChannelSchema = z.enum(ReleaseChannel).describe('Release channel').meta({ id: 'ReleaseChannel' }); + const SystemConfigNewVersionCheckSchema = z - .object({ enabled: configBool.describe('Enabled') }) + .object({ enabled: configBool.describe('Enabled'), channel: ReleaseChannelSchema }) .meta({ id: 'SystemConfigNewVersionCheckDto' }); const SystemConfigNightlyTasksSchema = z diff --git a/server/src/dtos/tag.dto.ts b/server/src/dtos/tag.dto.ts index 67dbca9914..dd679c97cb 100644 --- a/server/src/dtos/tag.dto.ts +++ b/server/src/dtos/tag.dto.ts @@ -1,21 +1,21 @@ import { createZodDto } from 'nestjs-zod'; import { Tag } from 'src/database'; import { MaybeDehydrated } from 'src/types'; -import { asDateString } from 'src/utils/date'; -import { emptyStringToNull, hexColor } from 'src/validation'; +import { asDateTimeString } from 'src/utils/date'; +import { hexColor } from 'src/validation'; import z from 'zod'; const TagCreateSchema = z .object({ name: z.string().describe('Tag name'), parentId: z.uuidv4().nullish().describe('Parent tag ID'), - color: emptyStringToNull(hexColor.nullable()).optional().describe('Tag color (hex)'), + color: hexColor.nullable().optional().describe('Tag color (hex)'), }) .meta({ id: 'TagCreateDto' }); const TagUpdateSchema = z .object({ - color: emptyStringToNull(hexColor.nullable()).optional().describe('Tag color (hex)'), + color: hexColor.nullable().optional().describe('Tag color (hex)'), }) .meta({ id: 'TagUpdateDto' }); @@ -65,8 +65,8 @@ export function mapTag(entity: MaybeDehydrated): TagResponseDto { parentId: entity.parentId ?? undefined, name: entity.value.split('/').at(-1) as string, value: entity.value, - createdAt: asDateString(entity.createdAt), - updatedAt: asDateString(entity.updatedAt), + createdAt: asDateTimeString(entity.createdAt), + updatedAt: asDateTimeString(entity.updatedAt), color: entity.color ?? undefined, }; } diff --git a/server/src/dtos/time-bucket.dto.ts b/server/src/dtos/time-bucket.dto.ts index 88a352e20e..a2ee06c878 100644 --- a/server/src/dtos/time-bucket.dto.ts +++ b/server/src/dtos/time-bucket.dto.ts @@ -1,6 +1,6 @@ import { createZodDto } from 'nestjs-zod'; import { BBoxSchema } from 'src/dtos/bbox.dto'; -import { AssetOrderSchema, AssetVisibilitySchema } from 'src/enum'; +import { AssetOrderBySchema, AssetOrderSchema, AssetVisibilitySchema } from 'src/enum'; import { stringToBool } from 'src/validation'; import z from 'zod'; @@ -23,6 +23,9 @@ const TimeBucketQueryBaseSchema = z order: AssetOrderSchema.optional().describe( 'Sort order for assets within time buckets (ASC for oldest first, DESC for newest first)', ), + orderBy: AssetOrderBySchema.optional().describe( + 'Date to group and order assets by (takenAt for date taken, createdAt for date added to Immich)', + ), visibility: AssetVisibilitySchema.optional().describe( 'Filter by asset visibility status (ARCHIVE, TIMELINE, HIDDEN, LOCKED)', ), @@ -82,6 +85,9 @@ const TimeBucketAssetResponseSchema = z thumbhash: z .array(z.string().nullable()) .describe('Array of BlurHash strings for generating asset previews (base64 encoded)'), + createdAt: z + .array(z.string()) + .describe('Array of UTC timestamps when each asset was originally uploaded to Immich'), fileCreatedAt: z.array(z.string()).describe('Array of file creation timestamps in UTC'), localOffsetHours: z .array(z.number()) @@ -101,8 +107,8 @@ const TimeBucketAssetResponseSchema = z livePhotoVideoId: z .array(z.string().nullable()) .describe('Array of live photo video asset IDs (null for non-live photos)'), - city: z.array(z.string().nullable()).describe('Array of city names extracted from EXIF GPS data'), - country: z.array(z.string().nullable()).describe('Array of country names extracted from EXIF GPS data'), + city: z.array(z.string().nullable()).optional().describe('Array of city names extracted from EXIF GPS data'), + country: z.array(z.string().nullable()).optional().describe('Array of country names extracted from EXIF GPS data'), latitude: z .array(z.number().nullable()) .optional() diff --git a/server/src/dtos/user-preferences.dto.ts b/server/src/dtos/user-preferences.dto.ts index 7a7c1d2558..b8894e4d51 100644 --- a/server/src/dtos/user-preferences.dto.ts +++ b/server/src/dtos/user-preferences.dto.ts @@ -45,6 +45,7 @@ const PeopleUpdateSchema = z .object({ enabled: z.boolean().optional().describe('Whether people are enabled'), sidebarWeb: z.boolean().optional().describe('Whether people appear in web sidebar'), + minimumFaces: z.int().min(1).optional().describe('People face threshold'), }) .optional() .meta({ id: 'PeopleUpdate' }); @@ -138,6 +139,7 @@ const PeopleResponseSchema = z .object({ enabled: z.boolean().describe('Whether people are enabled'), sidebarWeb: z.boolean().describe('Whether people appear in web sidebar'), + minimumFaces: z.int().min(1).optional().describe('People face threshold'), }) .meta({ id: 'PeopleResponse' }); diff --git a/server/src/dtos/user.dto.ts b/server/src/dtos/user.dto.ts index 75256b9e1a..528163e57c 100644 --- a/server/src/dtos/user.dto.ts +++ b/server/src/dtos/user.dto.ts @@ -3,8 +3,8 @@ import { User, UserAdmin } from 'src/database'; import { pinCodeRegex } from 'src/dtos/auth.dto'; import { UserAvatarColor, UserAvatarColorSchema, UserMetadataKey, UserStatusSchema } from 'src/enum'; import { MaybeDehydrated, UserMetadataItem } from 'src/types'; -import { asDateString } from 'src/utils/date'; -import { emptyStringToNull, isoDatetimeToDate, sanitizeFilename, stringToBool, toEmail } from 'src/validation'; +import { asDateTimeString } from 'src/utils/date'; +import { isoDatetimeToDate, sanitizeFilename, stringToBool, toEmail } from 'src/validation'; import z from 'zod'; export const UserUpdateMeSchema = z @@ -61,7 +61,7 @@ export const mapUser = (entity: MaybeDehydrated): UserResponse name: entity.name, profileImagePath: entity.profileImagePath, avatarColor: entity.avatarColor ?? emailToAvatarColor(entity.email), - profileChangedAt: asDateString(entity.profileChangedAt), + profileChangedAt: asDateTimeString(entity.profileChangedAt), }; }; @@ -80,10 +80,7 @@ export const UserAdminCreateSchema = z password: z.string().describe('User password'), name: z.string().describe('User name'), avatarColor: UserAvatarColorSchema.nullish(), - pinCode: emptyStringToNull(z.string().regex(pinCodeRegex).nullable()) - .optional() - .describe('PIN code') - .meta({ example: '123456' }), + pinCode: z.string().regex(pinCodeRegex).nullable().optional().describe('PIN code').meta({ example: '123456' }), storageLabel: z.string().pipe(sanitizeFilename).nullish().describe('Storage label'), quotaSizeInBytes: z.int().min(0).nullish().describe('Storage quota in bytes'), shouldChangePassword: z.boolean().optional().describe('Require password change on next login'), @@ -98,10 +95,7 @@ const UserAdminUpdateSchema = z .object({ email: toEmail.optional().describe('User email'), password: z.string().optional().describe('User password'), - pinCode: emptyStringToNull(z.string().regex(pinCodeRegex).nullable()) - .optional() - .describe('PIN code') - .meta({ example: '123456' }), + pinCode: z.string().regex(pinCodeRegex).nullable().optional().describe('PIN code').meta({ example: '123456' }), name: z.string().optional().describe('User name'), avatarColor: UserAvatarColorSchema.nullish(), storageLabel: z.string().pipe(sanitizeFilename).nullish().describe('Storage label'), diff --git a/server/src/dtos/workflow.dto.ts b/server/src/dtos/workflow.dto.ts index f94e4bed92..1a2c2ac9f9 100644 --- a/server/src/dtos/workflow.dto.ts +++ b/server/src/dtos/workflow.dto.ts @@ -1,101 +1,135 @@ +import type { WorkflowStepConfig, WorkflowTrigger } from '@immich/plugin-sdk'; import { createZodDto } from 'nestjs-zod'; -import type { WorkflowAction, WorkflowFilter } from 'src/database'; -import { PluginTriggerTypeSchema } from 'src/enum'; -import { ActionConfigSchema, FilterConfigSchema } from 'src/types/plugin-schema.types'; +import { WorkflowTriggerSchema, WorkflowTypeSchema } from 'src/enum'; import z from 'zod'; -const WorkflowFilterItemSchema = z +const WorkflowTriggerResponseSchema = z .object({ - pluginFilterId: z.uuidv4().describe('Plugin filter ID'), - filterConfig: FilterConfigSchema.optional(), + trigger: WorkflowTriggerSchema.describe('Trigger type'), + types: z.array(WorkflowTypeSchema).describe('Workflow types'), }) - .meta({ id: 'WorkflowFilterItemDto' }); + .meta({ id: 'WorkflowTriggerResponseDto' }); -const WorkflowActionItemSchema = z +const WorkflowSearchSchema = z .object({ - pluginActionId: z.uuidv4().describe('Plugin action ID'), - actionConfig: ActionConfigSchema.optional(), + id: z.uuidv4().optional().describe('Workflow ID'), + trigger: WorkflowTriggerSchema.optional().describe('Workflow trigger type'), + name: z.string().optional().describe('Workflow name'), + description: z.string().optional().describe('Workflow description'), + enabled: z.boolean().optional().describe('Workflow enabled'), }) - .meta({ id: 'WorkflowActionItemDto' }); + .meta({ id: 'WorkflowSearchDto' }); + +const WorkflowStepSchema = z + .object({ + method: z.string().describe('Step plugin method'), + config: z.record(z.string(), z.unknown()).nullable().describe('Step configuration'), + enabled: z.boolean().optional().describe('Step is enabled'), + }) + .meta({ id: 'WorkflowStepDto' }); + +const WorkflowShareStepSchema = z + .object({ + method: z.string().describe('Step plugin method'), + config: z.record(z.string(), z.unknown()).nullable().describe('Step configuration'), + enabled: z.boolean().optional().describe('Step is enabled'), + }) + .meta({ id: 'WorkflowShareStepDto' }); const WorkflowCreateSchema = z .object({ - triggerType: PluginTriggerTypeSchema, - name: z.string().describe('Workflow name'), - description: z.string().optional().describe('Workflow description'), + trigger: WorkflowTriggerSchema.describe('Workflow trigger type'), + name: z.string().nullable().optional().describe('Workflow name'), + description: z.string().nullable().optional().describe('Workflow description'), enabled: z.boolean().optional().describe('Workflow enabled'), - filters: z.array(WorkflowFilterItemSchema).describe('Workflow filters'), - actions: z.array(WorkflowActionItemSchema).describe('Workflow actions'), + steps: z.array(WorkflowStepSchema).optional(), }) .meta({ id: 'WorkflowCreateDto' }); const WorkflowUpdateSchema = z .object({ - triggerType: PluginTriggerTypeSchema.optional(), - name: z.string().optional().describe('Workflow name'), - description: z.string().optional().describe('Workflow description'), + trigger: WorkflowTriggerSchema.optional().describe('Workflow trigger type'), + name: z.string().nullable().optional().describe('Workflow name'), + description: z.string().nullable().optional().describe('Workflow description'), enabled: z.boolean().optional().describe('Workflow enabled'), - filters: z.array(WorkflowFilterItemSchema).optional().describe('Workflow filters'), - actions: z.array(WorkflowActionItemSchema).optional().describe('Workflow actions'), + steps: z.array(WorkflowStepSchema).optional(), }) .meta({ id: 'WorkflowUpdateDto' }); -const WorkflowFilterResponseSchema = z - .object({ - id: z.string().describe('Filter ID'), - workflowId: z.string().describe('Workflow ID'), - pluginFilterId: z.string().describe('Plugin filter ID'), - filterConfig: FilterConfigSchema.nullable(), - order: z.int().describe('Filter order'), - }) - .meta({ id: 'WorkflowFilterResponseDto' }); - -const WorkflowActionResponseSchema = z - .object({ - id: z.string().describe('Action ID'), - workflowId: z.string().describe('Workflow ID'), - pluginActionId: z.string().describe('Plugin action ID'), - actionConfig: ActionConfigSchema.nullable(), - order: z.int().describe('Action order'), - }) - .meta({ id: 'WorkflowActionResponseDto' }); - const WorkflowResponseSchema = z .object({ id: z.string().describe('Workflow ID'), - ownerId: z.string().describe('Owner user ID'), - triggerType: PluginTriggerTypeSchema, + trigger: WorkflowTriggerSchema.describe('Workflow trigger type'), name: z.string().nullable().describe('Workflow name'), - description: z.string().describe('Workflow description'), + description: z.string().nullable().describe('Workflow description'), createdAt: z.string().describe('Creation date'), + updatedAt: z.string().describe('Update date'), enabled: z.boolean().describe('Workflow enabled'), - filters: z.array(WorkflowFilterResponseSchema).describe('Workflow filters'), - actions: z.array(WorkflowActionResponseSchema).describe('Workflow actions'), + steps: z.array(WorkflowStepSchema).describe('Workflow steps'), }) .meta({ id: 'WorkflowResponseDto' }); +const WorkflowShareResponseSchema = z + .object({ + trigger: WorkflowTriggerSchema.describe('Workflow trigger type'), + name: z.string().nullable().describe('Workflow name'), + description: z.string().nullable().describe('Workflow description'), + steps: z.array(WorkflowShareStepSchema).describe('Workflow steps'), + }) + .meta({ id: 'WorkflowShareResponseDto' }); + +export class WorkflowTriggerResponseDto extends createZodDto(WorkflowTriggerResponseSchema) {} +export class WorkflowSearchDto extends createZodDto(WorkflowSearchSchema) {} export class WorkflowCreateDto extends createZodDto(WorkflowCreateSchema) {} export class WorkflowUpdateDto extends createZodDto(WorkflowUpdateSchema) {} export class WorkflowResponseDto extends createZodDto(WorkflowResponseSchema) {} -class WorkflowFilterResponseDto extends createZodDto(WorkflowFilterResponseSchema) {} -class WorkflowActionResponseDto extends createZodDto(WorkflowActionResponseSchema) {} +export class WorkflowShareResponseDto extends createZodDto(WorkflowShareResponseSchema) {} -export function mapWorkflowFilter(filter: WorkflowFilter): WorkflowFilterResponseDto { - return { - id: filter.id, - workflowId: filter.workflowId, - pluginFilterId: filter.pluginFilterId, - filterConfig: filter.filterConfig, - order: filter.order, - }; -} +type Workflow = { + id: string; + createdAt: Date; + updatedAt: Date; + trigger: WorkflowTrigger; + name: string | null; + description: string | null; + enabled: boolean; +}; -export function mapWorkflowAction(action: WorkflowAction): WorkflowActionResponseDto { +type WorkflowStep = { + enabled: boolean; + methodName: string; + config: WorkflowStepConfig | null; + pluginName: string; +}; + +export const mapWorkflow = (workflow: Workflow & { steps: WorkflowStep[] }): WorkflowResponseDto => { return { - id: action.id, - workflowId: action.workflowId, - pluginActionId: action.pluginActionId, - actionConfig: action.actionConfig, - order: action.order, + id: workflow.id, + enabled: workflow.enabled, + trigger: workflow.trigger, + name: workflow.name, + description: workflow.description, + createdAt: workflow.createdAt.toISOString(), + updatedAt: workflow.updatedAt.toISOString(), + steps: workflow.steps.map((step) => ({ + method: `${step.pluginName}#${step.methodName}`, + // TODO fix this + config: step.config as any, + enabled: step.enabled, + })), }; -} +}; + +export const mapWorkflowShare = (workflow: Workflow & { steps: WorkflowStep[] }): WorkflowShareResponseDto => { + return { + trigger: workflow.trigger, + name: workflow.name, + description: workflow.description, + steps: workflow.steps.map((step) => ({ + method: `${step.pluginName}#${step.methodName}`, + // TODO fix this + config: step.config as any, + enabled: step.enabled ? undefined : false, + })), + }; +}; diff --git a/server/src/enum.ts b/server/src/enum.ts index 26db386348..036ed9d7c4 100644 --- a/server/src/enum.ts +++ b/server/src/enum.ts @@ -1,3 +1,4 @@ +import { WorkflowTrigger } from '@immich/plugin-sdk'; import z from 'zod'; export enum AuthType { @@ -74,6 +75,13 @@ export enum AssetOrder { export const AssetOrderSchema = z.enum(AssetOrder).describe('Asset sort order').meta({ id: 'AssetOrder' }); +export enum AssetOrderBy { + TakenAt = 'takenAt', + CreatedAt = 'createdAt', +} + +export const AssetOrderBySchema = z.enum(AssetOrderBy).describe('Asset sorting property').meta({ id: 'AssetOrderBy' }); + export enum MemoryType { /** pictures taken on this day X years ago */ OnThisDay = 'on_this_day', @@ -445,17 +453,11 @@ export enum VideoCodec { export const VideoCodecSchema = z.enum(VideoCodec).describe('Target video codec').meta({ id: 'VideoCodec' }); -export enum VideoSegmentCodec { - Av1 = 'av1', - Hevc = 'hevc', - H264 = 'h264', -} +export type VideoSegmentCodec = VideoCodec.Av1 | VideoCodec.Hevc | VideoCodec.H264; export enum AudioCodec { Mp3 = 'mp3', Aac = 'aac', - /** @deprecated Use `Opus` instead */ - Libopus = 'libopus', Opus = 'opus', PcmS16le = 'pcm_s16le', } @@ -744,8 +746,11 @@ export enum BootstrapEventPriority { StorageService = -195, // Other services may need to queue jobs on bootstrap. JobService = -190, - // Initialise config after other bootstrap services, stop other services from using config on bootstrap + // Initialize config after other bootstrap services, stop other services from using config on bootstrap SystemConfig = 100, + PluginSync = 190, + // Load plugins into memory after sync + PluginLoad = 200, } export enum QueueName { @@ -818,6 +823,8 @@ export enum JobName { LibrarySyncFiles = 'LibrarySyncFiles', LibraryScanQueueAll = 'LibraryScanQueueAll', + HlsSessionCleanup = 'HlsSessionCleanup', + MemoryCleanup = 'MemoryCleanup', MemoryGenerate = 'MemoryGenerate', @@ -858,7 +865,7 @@ export enum JobName { Ocr = 'Ocr', // Workflow - WorkflowRun = 'WorkflowRun', + WorkflowAssetTrigger = 'WorkflowAssetTrigger', } export const JobNameSchema = z.enum(JobName).describe('Job name').meta({ id: 'JobName' }); @@ -904,12 +911,14 @@ export enum DatabaseLock { CLIPDimSize = 512, Library = 1337, NightlyJobs = 600, + PluginImport = 666, MediaLocation = 700, GetSystemConfig = 69, BackupDatabase = 42, MaintenanceOperation = 621, MemoryCreation = 777, VersionCheck = 800, + HlsSessionCleanup = 850, } export enum MaintenanceAction { @@ -1158,12 +1167,14 @@ export enum PluginContext { export const PluginContextSchema = z.enum(PluginContext).describe('Plugin context').meta({ id: 'PluginContextType' }); -export enum PluginTriggerType { - AssetCreate = 'AssetCreate', - PersonRecognized = 'PersonRecognized', +export const WorkflowTriggerSchema = z + .enum(WorkflowTrigger) + .describe('Plugin trigger type') + .meta({ id: 'WorkflowTrigger' }); + +export enum WorkflowType { + AssetV1 = 'AssetV1', + AssetPersonV1 = 'AssetPersonV1', } -export const PluginTriggerTypeSchema = z - .enum(PluginTriggerType) - .describe('Plugin trigger type') - .meta({ id: 'PluginTriggerType' }); +export const WorkflowTypeSchema = z.enum(WorkflowType).describe('Workflow type').meta({ id: 'WorkflowType' }); diff --git a/server/src/middleware/error.interceptor.ts b/server/src/middleware/error.interceptor.ts index 3c0c09aa54..2cf5369e98 100644 --- a/server/src/middleware/error.interceptor.ts +++ b/server/src/middleware/error.interceptor.ts @@ -1,14 +1,14 @@ import { CallHandler, ExecutionContext, - HttpException, Injectable, InternalServerErrorException, NestInterceptor, } from '@nestjs/common'; +import { Request } from 'express'; import { Observable, catchError, throwError } from 'rxjs'; import { LoggingRepository } from 'src/repositories/logging.repository'; -import { logGlobalError } from 'src/utils/logger'; +import { isHttpException, onRequestError } from 'src/utils/logger'; import { routeToErrorMessage } from 'src/utils/misc'; @Injectable() @@ -18,14 +18,16 @@ export class ErrorInterceptor implements NestInterceptor { } intercept(context: ExecutionContext, next: CallHandler): Observable { + const req = context.switchToHttp().getRequest(); + return next.handle().pipe( catchError((error) => throwError(() => { - if (error instanceof HttpException) { + if (isHttpException(error)) { return error; } - logGlobalError(this.logger, error); + onRequestError(req, error, this.logger); const message = routeToErrorMessage(context.getHandler().name); return new InternalServerErrorException(message); diff --git a/server/src/middleware/file-upload.interceptor.ts b/server/src/middleware/file-upload.interceptor.ts index 63acb13789..1bbab36a87 100644 --- a/server/src/middleware/file-upload.interceptor.ts +++ b/server/src/middleware/file-upload.interceptor.ts @@ -96,7 +96,11 @@ export class FileUploadInterceptor implements NestInterceptor { private handleFile(request: AuthRequest, file: Express.Multer.File, callback: Callback>) { request.on('error', (error) => { - this.logger.warn('Request error while uploading file, cleaning up', error); + if ('code' in error && error.code === 'ECONNRESET') { + this.logger.debug('Upload was cancelled'); + } else { + this.logger.error(`Upload failed with: ${error}`); + } this.assetService.onUploadError(request, file).catch(this.logger.error); }); diff --git a/server/src/middleware/global-exception.filter.ts b/server/src/middleware/global-exception.filter.ts index 7572274d15..67bba5c358 100644 --- a/server/src/middleware/global-exception.filter.ts +++ b/server/src/middleware/global-exception.filter.ts @@ -1,10 +1,10 @@ import { ArgumentsHost, Catch, ExceptionFilter, HttpException } from '@nestjs/common'; -import { Response } from 'express'; +import { Request, Response } from 'express'; import { ClsService } from 'nestjs-cls'; import { ZodSerializationException, ZodValidationException } from 'nestjs-zod'; import { ImmichHeader } from 'src/enum'; import { LoggingRepository } from 'src/repositories/logging.repository'; -import { logGlobalError } from 'src/utils/logger'; +import { onRequestError } from 'src/utils/logger'; import { ZodError } from 'zod'; @Catch() @@ -17,10 +17,13 @@ export class GlobalExceptionFilter implements ExceptionFilter { } catch(error: Error, host: ArgumentsHost) { - this.handleError(host.switchToHttp().getResponse(), error); + const http = host.switchToHttp(); + this.handleError(http.getRequest(), http.getResponse(), error); } - handleError(res: Response, error: Error) { + handleError(req: Request, res: Response, error: Error) { + onRequestError(req, error, this.logger); + const { status, body } = this.fromError(error); if (!res.headersSent) { res.header(ImmichHeader.CorrelationId, this.cls.getId()).status(status).json(body); @@ -28,8 +31,6 @@ export class GlobalExceptionFilter implements ExceptionFilter { } private fromError(error: Error) { - logGlobalError(this.logger, error); - if (error instanceof HttpException) { const status = error.getStatus(); const response = error.getResponse(); diff --git a/server/src/plugins.ts b/server/src/plugins.ts deleted file mode 100644 index 77f35e79f6..0000000000 --- a/server/src/plugins.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { PluginContext, PluginTriggerType } from 'src/enum'; - -export type PluginTrigger = { - type: PluginTriggerType; - contextType: PluginContext; -}; - -export const pluginTriggers: PluginTrigger[] = [ - { - type: PluginTriggerType.AssetCreate, - contextType: PluginContext.Asset, - }, - { - type: PluginTriggerType.PersonRecognized, - contextType: PluginContext.Person, - }, -]; diff --git a/server/src/queries/album.repository.sql b/server/src/queries/album.repository.sql index f5bf79ac9e..35c82fdb73 100644 --- a/server/src/queries/album.repository.sql +++ b/server/src/queries/album.repository.sql @@ -185,7 +185,7 @@ where group by "album_asset"."albumId" --- AlbumRepository.getOwned +-- AlbumRepository.getAll select "album".*, ( @@ -242,172 +242,55 @@ from "album" inner join "album_user" on "album_user"."albumId" = "album"."id" and "album_user"."userId" = $2 - and "album_user"."role" = 'owner' where "album"."deletedAt" is null -order by - "album"."createdAt" desc - --- AlbumRepository.getShared -select - "album".*, - ( - select - coalesce(json_agg(agg), '[]') - from - ( - select - "album_user"."role", - ( - select - to_json(obj) - from - ( - select - "id", - "name", - "email", - "avatarColor", - "profileImagePath", - "profileChangedAt" - from - ( - select - 1 - ) as "dummy" - ) as obj - ) as "user" - from - "album_user" - inner join "user" on "user"."id" = "album_user"."userId" - where - "album_user"."albumId" = "album"."id" - order by - "album_user"."role", - "album_user"."userId" = $1 desc, - "user"."name" asc - ) as agg - ) as "albumUsers", - ( - select - coalesce(json_agg(agg), '[]') - from - ( - select - "shared_link".* - from - "shared_link" - where - "shared_link"."albumId" = "album"."id" - ) as agg - ) as "sharedLinks" -from - "album" - inner join ( - select - "album_user"."albumId" as "id" - from - "album_user" - where - "album_user"."userId" = $2 - and "album_user"."albumId" in ( - select - "album_user"."albumId" - from - "album_user" - where - "album_user"."role" != 'owner' - ) - union - select - "shared_link"."albumId" as "id" - from - "shared_link" - where - "shared_link"."userId" = $3 - and "shared_link"."albumId" is not null - ) as "matching" on "matching"."id" = "album"."id" - inner join "album_user" on "album_user"."albumId" = "album"."id" and "album_user"."role" = 'owner' -where - "album"."deletedAt" is null -order by - "album"."createdAt" desc - --- AlbumRepository.getNotShared -select - "album".*, - ( - select - coalesce(json_agg(agg), '[]') - from - ( - select - "shared_link".* - from - "shared_link" - where - "shared_link"."albumId" = "album"."id" - ) as agg - ) as "sharedLinks", - ( - select - coalesce(json_agg(agg), '[]') - from - ( - select - "album_user"."role", - ( - select - to_json(obj) - from - ( - select - "id", - "name", - "email", - "avatarColor", - "profileImagePath", - "profileChangedAt" - from - ( - select - 1 - ) as "dummy" - ) as obj - ) as "user" - from - "album_user" - inner join "user" on "user"."id" = "album_user"."userId" - where - "album_user"."albumId" = "album"."id" - order by - "album_user"."role", - "album_user"."userId" = $1 desc, - "user"."name" asc - ) as agg - ) as "albumUsers" -from - "album" - inner join "album_user" on "album_user"."albumId" = "album"."id" - and "album_user"."userId" = $2 - and "album_user"."role" = 'owner' -where - "album"."deletedAt" is null - and not exists ( - select - from - "album_user" as "au" - where - "au"."albumId" = "album"."id" - and "au"."role" != 'owner' + and ( + exists ( + select + from + "album_user" as "au" + where + "au"."albumId" = "album"."id" + and "au"."role" != 'owner' + ) + or exists ( + select + from + "shared_link" + where + "shared_link"."albumId" = "album"."id" + ) ) - and not exists ( - select - from - "shared_link" - where - "shared_link"."albumId" = "album"."id" +order by + "album"."createdAt" desc + +-- AlbumRepository.getAllIds +select + "album"."id" +from + "album" + inner join "album_user" on "album_user"."albumId" = "album"."id" + and "album_user"."userId" = $1 +where + "album"."deletedAt" is null + and "album_user"."role" = 'owner' + and ( + exists ( + select + from + "album_user" as "au" + where + "au"."albumId" = "album"."id" + and "au"."role" != 'owner' + ) + or exists ( + select + from + "shared_link" + where + "shared_link"."albumId" = "album"."id" + ) ) order by "album"."createdAt" desc diff --git a/server/src/queries/asset.repository.sql b/server/src/queries/asset.repository.sql index ebc2de90e1..819ec4f838 100644 --- a/server/src/queries/asset.repository.sql +++ b/server/src/queries/asset.repository.sql @@ -382,9 +382,8 @@ with "asset"."ownerId", "asset"."status", asset."fileCreatedAt" at time zone 'utc' as "fileCreatedAt", + asset."createdAt" at time zone 'utc' as "createdAt", encode("asset"."thumbhash", 'base64') as "thumbhash", - "asset_exif"."city", - "asset_exif"."country", "asset_exif"."projectionType", coalesce( case @@ -397,6 +396,8 @@ with end, 1 ) as "ratio", + "asset_exif"."city", + "asset_exif"."country", "stack" from "asset" @@ -431,8 +432,6 @@ with ), "agg" as ( select - coalesce(array_agg("city"), '{}') as "city", - coalesce(array_agg("country"), '{}') as "country", coalesce(array_agg("duration"), '{}') as "duration", coalesce(array_agg("id"), '{}') as "id", coalesce(array_agg("visibility"), '{}') as "visibility", @@ -442,11 +441,14 @@ with coalesce(array_agg("livePhotoVideoId"), '{}') as "livePhotoVideoId", coalesce(array_agg("fileCreatedAt"), '{}') as "fileCreatedAt", coalesce(array_agg("localOffsetHours"), '{}') as "localOffsetHours", + coalesce(array_agg("createdAt"), '{}') as "createdAt", coalesce(array_agg("ownerId"), '{}') as "ownerId", coalesce(array_agg("projectionType"), '{}') as "projectionType", coalesce(array_agg("ratio"), '{}') as "ratio", coalesce(array_agg("status"), '{}') as "status", coalesce(array_agg("thumbhash"), '{}') as "thumbhash", + coalesce(array_agg("city"), '{}') as "city", + coalesce(array_agg("country"), '{}') as "country", coalesce(json_agg("stack"), '[]') as "stack" from "cte" @@ -485,6 +487,22 @@ where limit $5 +-- AssetRepository.getRecentlyCreatedAssetIds +select + "id" as "data", + "createdAt" as "value" +from + "asset" +where + "ownerId" = $1::uuid + and "asset"."visibility" = $2 + and "type" = $3 + and "deletedAt" is null +order by + "value" desc +limit + $4 + -- AssetRepository.detectOfflineExternalAssets update "asset" set diff --git a/server/src/queries/memory.repository.sql b/server/src/queries/memory.repository.sql index da970c2c78..44339cbcd9 100644 --- a/server/src/queries/memory.repository.sql +++ b/server/src/queries/memory.repository.sql @@ -42,6 +42,16 @@ select "memory_asset"."memoriesId" = "memory"."id" and "asset"."visibility" = 'timeline' and "asset"."deletedAt" is null + and not exists ( + select + $1 as "one" + from + "asset_face" + inner join "person" on "person"."id" = "asset_face"."personId" + where + "asset_face"."assetId" = "asset"."id" + and "person"."isHidden" = $2 + ) order by "asset"."fileCreatedAt" asc ) as agg @@ -51,7 +61,7 @@ from "memory" where "deletedAt" is null - and "ownerId" = $1 + and "ownerId" = $3 order by "memoryAt" desc @@ -71,6 +81,16 @@ select "memory_asset"."memoriesId" = "memory"."id" and "asset"."visibility" = 'timeline' and "asset"."deletedAt" is null + and not exists ( + select + $1 as "one" + from + "asset_face" + inner join "person" on "person"."id" = "asset_face"."personId" + where + "asset_face"."assetId" = "asset"."id" + and "person"."isHidden" = $2 + ) order by "asset"."fileCreatedAt" asc ) as agg @@ -81,14 +101,14 @@ from where ( "showAt" is null - or "showAt" <= $1 + or "showAt" <= $3 ) and ( "hideAt" is null - or "hideAt" >= $2 + or "hideAt" >= $4 ) and "deletedAt" is null - and "ownerId" = $3 + and "ownerId" = $5 order by "memoryAt" desc diff --git a/server/src/queries/person.repository.sql b/server/src/queries/person.repository.sql index 318c151cca..a2f3f64442 100644 --- a/server/src/queries/person.repository.sql +++ b/server/src/queries/person.repository.sql @@ -42,7 +42,18 @@ group by having ( "person"."name" != $3 - or count("asset_face"."assetId") >= $4 + or count("asset_face"."assetId") >= COALESCE( + ( + SELECT + value -> 'people' ->> 'minimumFaces' + FROM + user_metadata + WHERE + "userId" = $4 + AND key = 'preferences' + ), + '3' + )::int ) order by "person"."isHidden" asc, diff --git a/server/src/queries/plugin.repository.sql b/server/src/queries/plugin.repository.sql index 82c203dafd..824602ba86 100644 --- a/server/src/queries/plugin.repository.sql +++ b/server/src/queries/plugin.repository.sql @@ -1,159 +1,198 @@ -- NOTE: This file is auto generated by ./sql-generator --- PluginRepository.getPlugin +-- PluginRepository.getForLoad select - "plugin"."id" as "id", - "plugin"."name" as "name", - "plugin"."title" as "title", - "plugin"."description" as "description", - "plugin"."author" as "author", - "plugin"."version" as "version", - "plugin"."wasmPath" as "wasmPath", - "plugin"."createdAt" as "createdAt", - "plugin"."updatedAt" as "updatedAt", + "plugin"."id", + "plugin"."name", + "plugin"."version", + "plugin"."wasmBytes", ( select coalesce(json_agg(agg), '[]') from ( select - * + "plugin_method"."name", + "plugin_method"."hostFunctions" from - "plugin_filter" + "plugin_method" where - "plugin_filter"."pluginId" = "plugin"."id" + "plugin_method"."pluginId" = "plugin"."id" ) as agg - ) as "filters", - ( - select - coalesce(json_agg(agg), '[]') - from - ( - select - * - from - "plugin_action" - where - "plugin_action"."pluginId" = "plugin"."id" - ) as agg - ) as "actions" + ) as "methods" from "plugin" where - "plugin"."id" = $1 + "enabled" = $1 --- PluginRepository.getPluginByName +-- PluginRepository.search select - "plugin"."id" as "id", - "plugin"."name" as "name", - "plugin"."title" as "title", - "plugin"."description" as "description", - "plugin"."author" as "author", - "plugin"."version" as "version", - "plugin"."wasmPath" as "wasmPath", - "plugin"."createdAt" as "createdAt", - "plugin"."updatedAt" as "updatedAt", + "plugin"."id", + "plugin"."name", + "plugin"."title", + "plugin"."description", + "plugin"."author", + "plugin"."version", + "plugin"."createdAt", + "plugin"."updatedAt", + "plugin"."templates", ( select coalesce(json_agg(agg), '[]') from ( select - * + "plugin_method"."name", + "plugin_method"."title", + "plugin_method"."description", + "plugin_method"."types", + "plugin_method"."schema", + "plugin_method"."hostFunctions", + "plugin_method"."uiHints", + "plugin"."name" as "pluginName" from - "plugin_filter" + "plugin_method" where - "plugin_filter"."pluginId" = "plugin"."id" + "plugin_method"."pluginId" = "plugin"."id" ) as agg - ) as "filters", - ( - select - coalesce(json_agg(agg), '[]') - from - ( - select - * - from - "plugin_action" - where - "plugin_action"."pluginId" = "plugin"."id" - ) as agg - ) as "actions" -from - "plugin" -where - "plugin"."name" = $1 - --- PluginRepository.getAllPlugins -select - "plugin"."id" as "id", - "plugin"."name" as "name", - "plugin"."title" as "title", - "plugin"."description" as "description", - "plugin"."author" as "author", - "plugin"."version" as "version", - "plugin"."wasmPath" as "wasmPath", - "plugin"."createdAt" as "createdAt", - "plugin"."updatedAt" as "updatedAt", - ( - select - coalesce(json_agg(agg), '[]') - from - ( - select - * - from - "plugin_filter" - where - "plugin_filter"."pluginId" = "plugin"."id" - ) as agg - ) as "filters", - ( - select - coalesce(json_agg(agg), '[]') - from - ( - select - * - from - "plugin_action" - where - "plugin_action"."pluginId" = "plugin"."id" - ) as agg - ) as "actions" + ) as "methods" from "plugin" order by "plugin"."name" --- PluginRepository.getFilter +-- PluginRepository.getByHash select - * + "plugin"."id", + "plugin"."name", + "plugin"."title", + "plugin"."description", + "plugin"."author", + "plugin"."version", + "plugin"."createdAt", + "plugin"."updatedAt", + "plugin"."templates", + ( + select + coalesce(json_agg(agg), '[]') + from + ( + select + "plugin_method"."name", + "plugin_method"."title", + "plugin_method"."description", + "plugin_method"."types", + "plugin_method"."schema", + "plugin_method"."hostFunctions", + "plugin_method"."uiHints", + "plugin"."name" as "pluginName" + from + "plugin_method" + where + "plugin_method"."pluginId" = "plugin"."id" + ) as agg + ) as "methods" from - "plugin_filter" + "plugin" where - "id" = $1 + "plugin"."sha256hash" = $1 --- PluginRepository.getFiltersByPlugin +-- PluginRepository.getByName select - * + "plugin"."id", + "plugin"."name", + "plugin"."title", + "plugin"."description", + "plugin"."author", + "plugin"."version", + "plugin"."createdAt", + "plugin"."updatedAt", + "plugin"."templates", + ( + select + coalesce(json_agg(agg), '[]') + from + ( + select + "plugin_method"."name", + "plugin_method"."title", + "plugin_method"."description", + "plugin_method"."types", + "plugin_method"."schema", + "plugin_method"."hostFunctions", + "plugin_method"."uiHints", + "plugin"."name" as "pluginName" + from + "plugin_method" + where + "plugin_method"."pluginId" = "plugin"."id" + ) as agg + ) as "methods" from - "plugin_filter" + "plugin" where - "pluginId" = $1 + "plugin"."name" = $1 --- PluginRepository.getAction +-- PluginRepository.get select - * + "plugin"."id", + "plugin"."name", + "plugin"."title", + "plugin"."description", + "plugin"."author", + "plugin"."version", + "plugin"."createdAt", + "plugin"."updatedAt", + "plugin"."templates", + ( + select + coalesce(json_agg(agg), '[]') + from + ( + select + "plugin_method"."name", + "plugin_method"."title", + "plugin_method"."description", + "plugin_method"."types", + "plugin_method"."schema", + "plugin_method"."hostFunctions", + "plugin_method"."uiHints", + "plugin"."name" as "pluginName" + from + "plugin_method" + where + "plugin_method"."pluginId" = "plugin"."id" + ) as agg + ) as "methods" from - "plugin_action" + "plugin" where - "id" = $1 + "plugin"."id" = $1 --- PluginRepository.getActionsByPlugin +-- PluginRepository.getForValidation select - * + "plugin_method"."id", + "plugin_method"."name", + "plugin"."name" as "pluginName", + "plugin_method"."types" from - "plugin_action" -where - "pluginId" = $1 + "plugin_method" + inner join "plugin" on "plugin_method"."pluginId" = "plugin"."id" + +-- PluginRepository.searchMethods +select + "plugin"."name" as "pluginName", + "plugin_method"."pluginId", + "plugin_method"."id", + "plugin_method"."name", + "plugin_method"."title", + "plugin_method"."description", + "plugin_method"."types", + "plugin_method"."schema", + "plugin_method"."hostFunctions", + "plugin_method"."uiHints" +from + "plugin_method" + inner join "plugin" on "plugin"."id" = "plugin_method"."pluginId" +order by + "plugin_method"."name" diff --git a/server/src/queries/sync.repository.sql b/server/src/queries/sync.repository.sql index 96d9205776..36265e8d63 100644 --- a/server/src/queries/sync.repository.sql +++ b/server/src/queries/sync.repository.sql @@ -65,6 +65,7 @@ select "asset"."checksum", "asset"."fileCreatedAt", "asset"."fileModifiedAt", + "asset"."createdAt", "asset"."localDateTime", "asset"."type", "asset"."deletedAt", @@ -98,6 +99,7 @@ select "asset"."checksum", "asset"."fileCreatedAt", "asset"."fileModifiedAt", + "asset"."createdAt", "asset"."localDateTime", "asset"."type", "asset"."deletedAt", @@ -133,6 +135,7 @@ select "asset"."checksum", "asset"."fileCreatedAt", "asset"."fileModifiedAt", + "asset"."createdAt", "asset"."localDateTime", "asset"."type", "asset"."deletedAt", @@ -407,6 +410,7 @@ select "asset"."checksum", "asset"."fileCreatedAt", "asset"."fileModifiedAt", + "asset"."createdAt", "asset"."localDateTime", "asset"."type", "asset"."deletedAt", @@ -779,9 +783,9 @@ select "asset"."fileCreatedAt", "asset"."fileModifiedAt", "asset"."localDateTime", + "asset"."createdAt", "asset"."type", "asset"."deletedAt", - "asset"."isFavorite", "asset"."visibility", "asset"."duration", "asset"."livePhotoVideoId", @@ -790,14 +794,15 @@ select "asset"."width", "asset"."height", "asset"."isEdited", + $1 as "isFavorite", "asset"."updateId" from "asset" as "asset" where - "asset"."updateId" < $1 - and "asset"."updateId" <= $2 - and "asset"."updateId" >= $3 - and "ownerId" = $4 + "asset"."updateId" < $2 + and "asset"."updateId" <= $3 + and "asset"."updateId" >= $4 + and "ownerId" = $5 order by "asset"."updateId" asc @@ -831,9 +836,9 @@ select "asset"."fileCreatedAt", "asset"."fileModifiedAt", "asset"."localDateTime", + "asset"."createdAt", "asset"."type", "asset"."deletedAt", - "asset"."isFavorite", "asset"."visibility", "asset"."duration", "asset"."livePhotoVideoId", @@ -842,19 +847,20 @@ select "asset"."width", "asset"."height", "asset"."isEdited", + $1 as "isFavorite", "asset"."updateId" from "asset" as "asset" where - "asset"."updateId" < $1 - and "asset"."updateId" > $2 + "asset"."updateId" < $2 + and "asset"."updateId" > $3 and "ownerId" in ( select "sharedById" from "partner" where - "sharedWithId" = $3 + "sharedWithId" = $4 ) order by "asset"."updateId" asc diff --git a/server/src/queries/video.stream.repository.sql b/server/src/queries/video.stream.repository.sql index c77882d77d..714e138ce8 100644 --- a/server/src/queries/video.stream.repository.sql +++ b/server/src/queries/video.stream.repository.sql @@ -7,6 +7,7 @@ from "video_stream_session" where "id" = $1 + and "expiresAt" > $2 -- VideoStreamRepository.getVariant select @@ -27,11 +28,13 @@ where -- VideoStreamRepository.getExpiredSessions select - "id" + "video_stream_session"."id", + "asset"."ownerId" from "video_stream_session" + inner join "asset" on "asset"."id" = "video_stream_session"."assetId" where - "expiresAt" <= $1 + "video_stream_session"."expiresAt" <= $1 -- VideoStreamRepository.extendSession update "video_stream_session" @@ -44,3 +47,253 @@ where delete from "video_stream_session" where "id" = $1 + +-- VideoStreamRepository.getForMainPlaylist +select + ( + select + to_json(obj) + from + ( + select + "asset_video"."index", + "asset_video"."codecName", + "asset_video"."profile", + "asset_video"."level", + "asset_video"."bitrate", + "asset_exif"."exifImageWidth" as "width", + "asset_exif"."exifImageHeight" as "height", + "asset_video"."pixelFormat", + "asset_video"."frameCount", + "asset_exif"."fps" as "frameRate", + "asset_video"."timeBase", + case + when "asset_exif"."orientation" = '6' then -90 + when "asset_exif"."orientation" = '8' then 90 + when "asset_exif"."orientation" = '3' then 180 + else 0 + end as "rotation", + "asset_video"."colorPrimaries", + "asset_video"."colorMatrix", + "asset_video"."colorTransfer", + "asset_video"."dvProfile", + "asset_video"."dvLevel", + "asset_video"."dvBlSignalCompatibilityId" + from + ( + select + 1 + ) as "dummy" + where + "asset_video"."assetId" is not null + ) as obj + ) as "videoStream", + ( + select + to_json(obj) + from + ( + select + "asset_keyframe"."pts" as "keyframePts", + "asset_keyframe"."accDuration" as "keyframeAccDuration", + "asset_keyframe"."ownDuration" as "keyframeOwnDuration", + "asset_keyframe"."totalDuration", + "asset_keyframe"."packetCount", + "asset_keyframe"."outputFrames" + from + ( + select + 1 + ) as "dummy" + where + "asset_keyframe"."assetId" is not null + ) as obj + ) as "packets" +from + "asset" + inner join "asset_exif" on "asset"."id" = "asset_exif"."assetId" + inner join "asset_video" on "asset"."id" = "asset_video"."assetId" + inner join "asset_keyframe" on "asset"."id" = "asset_keyframe"."assetId" +where + "asset"."id" = $1 + +-- VideoStreamRepository.getForMediaPlaylist +select + ( + select + to_json(obj) + from + ( + select + "asset_video"."index", + "asset_video"."codecName", + "asset_video"."profile", + "asset_video"."level", + "asset_video"."bitrate", + "asset_exif"."exifImageWidth" as "width", + "asset_exif"."exifImageHeight" as "height", + "asset_video"."pixelFormat", + "asset_video"."frameCount", + "asset_exif"."fps" as "frameRate", + "asset_video"."timeBase", + case + when "asset_exif"."orientation" = '6' then -90 + when "asset_exif"."orientation" = '8' then 90 + when "asset_exif"."orientation" = '3' then 180 + else 0 + end as "rotation", + "asset_video"."colorPrimaries", + "asset_video"."colorMatrix", + "asset_video"."colorTransfer", + "asset_video"."dvProfile", + "asset_video"."dvLevel", + "asset_video"."dvBlSignalCompatibilityId" + from + ( + select + 1 + ) as "dummy" + where + "asset_video"."assetId" is not null + ) as obj + ) as "videoStream", + ( + select + to_json(obj) + from + ( + select + "asset_keyframe"."pts" as "keyframePts", + "asset_keyframe"."accDuration" as "keyframeAccDuration", + "asset_keyframe"."ownDuration" as "keyframeOwnDuration", + "asset_keyframe"."totalDuration", + "asset_keyframe"."packetCount", + "asset_keyframe"."outputFrames" + from + ( + select + 1 + ) as "dummy" + where + "asset_keyframe"."assetId" is not null + ) as obj + ) as "packets" +from + "asset" + inner join "asset_exif" on "asset"."id" = "asset_exif"."assetId" + inner join "video_stream_session" on "asset"."id" = "video_stream_session"."assetId" + inner join "asset_video" on "asset"."id" = "asset_video"."assetId" + inner join "asset_keyframe" on "asset"."id" = "asset_keyframe"."assetId" +where + "asset"."id" = $1 + and "video_stream_session"."id" = $2 + and "video_stream_session"."expiresAt" > $3 + +-- VideoStreamRepository.getForTranscoding +select + "asset"."originalPath", + ( + select + to_json(obj) + from + ( + select + "asset_audio"."index", + "asset_audio"."codecName", + "asset_audio"."profile", + "asset_audio"."bitrate" + from + ( + select + 1 + ) as "dummy" + where + "asset_audio"."assetId" is not null + ) as obj + ) as "audioStream", + ( + select + to_json(obj) + from + ( + select + "asset_video"."index", + "asset_video"."codecName", + "asset_video"."profile", + "asset_video"."level", + "asset_video"."bitrate", + "asset_exif"."exifImageWidth" as "width", + "asset_exif"."exifImageHeight" as "height", + "asset_video"."pixelFormat", + "asset_video"."frameCount", + "asset_exif"."fps" as "frameRate", + "asset_video"."timeBase", + case + when "asset_exif"."orientation" = '6' then -90 + when "asset_exif"."orientation" = '8' then 90 + when "asset_exif"."orientation" = '3' then 180 + else 0 + end as "rotation", + "asset_video"."colorPrimaries", + "asset_video"."colorMatrix", + "asset_video"."colorTransfer", + "asset_video"."dvProfile", + "asset_video"."dvLevel", + "asset_video"."dvBlSignalCompatibilityId" + from + ( + select + 1 + ) as "dummy" + where + "asset_video"."assetId" is not null + ) as obj + ) as "videoStream", + ( + select + to_json(obj) + from + ( + select + "asset_video"."formatName", + "asset_video"."formatLongName", + "asset"."duration", + "asset_video"."bitrate" + from + ( + select + 1 + ) as "dummy" + where + "asset_video"."assetId" is not null + ) as obj + ) as "format", + ( + select + to_json(obj) + from + ( + select + "asset_keyframe"."pts" as "keyframePts", + "asset_keyframe"."accDuration" as "keyframeAccDuration", + "asset_keyframe"."ownDuration" as "keyframeOwnDuration", + "asset_keyframe"."totalDuration", + "asset_keyframe"."packetCount", + "asset_keyframe"."outputFrames" + from + ( + select + 1 + ) as "dummy" + where + "asset_keyframe"."assetId" is not null + ) as obj + ) as "packets" +from + "asset" + inner join "asset_exif" on "asset"."id" = "asset_exif"."assetId" + left join "asset_audio" on "asset"."id" = "asset_audio"."assetId" + inner join "asset_video" on "asset"."id" = "asset_video"."assetId" + inner join "asset_keyframe" on "asset"."id" = "asset_keyframe"."assetId" +where + "asset"."id" = $1 diff --git a/server/src/queries/workflow.repository.sql b/server/src/queries/workflow.repository.sql index 27dc21dffe..fb62a51a73 100644 --- a/server/src/queries/workflow.repository.sql +++ b/server/src/queries/workflow.repository.sql @@ -1,70 +1,101 @@ -- NOTE: This file is auto generated by ./sql-generator --- WorkflowRepository.getWorkflow +-- WorkflowRepository.search select - * + "workflow"."id", + "workflow"."name", + "workflow"."description", + "workflow"."trigger", + "workflow"."enabled", + "workflow"."createdAt", + "workflow"."updatedAt", + ( + select + coalesce(json_agg(agg), '[]') + from + ( + select + "plugin"."name" as "pluginName", + "plugin_method"."name" as "methodName", + "workflow_step"."config", + "workflow_step"."enabled" + from + "workflow_step" + inner join "plugin_method" on "plugin_method"."id" = "workflow_step"."pluginMethodId" + inner join "plugin" on "plugin"."id" = "plugin_method"."pluginId" + where + "workflow"."id" = "workflow_step"."workflowId" + ) as agg + ) as "steps" +from + "workflow" +order by + "createdAt" desc + +-- WorkflowRepository.get +select + "workflow"."id", + "workflow"."name", + "workflow"."description", + "workflow"."trigger", + "workflow"."enabled", + "workflow"."createdAt", + "workflow"."updatedAt", + ( + select + coalesce(json_agg(agg), '[]') + from + ( + select + "plugin"."name" as "pluginName", + "plugin_method"."name" as "methodName", + "workflow_step"."config", + "workflow_step"."enabled" + from + "workflow_step" + inner join "plugin_method" on "plugin_method"."id" = "workflow_step"."pluginMethodId" + inner join "plugin" on "plugin"."id" = "plugin_method"."pluginId" + where + "workflow"."id" = "workflow_step"."workflowId" + ) as agg + ) as "steps" from "workflow" where "id" = $1 -order by - "createdAt" desc --- WorkflowRepository.getWorkflowsByOwner +-- WorkflowRepository.getForWorkflowRun select - * + "workflow"."id", + "workflow"."name", + "workflow"."trigger", + ( + select + coalesce(json_agg(agg), '[]') + from + ( + select + "workflow_step"."id", + "workflow_step"."config", + "plugin_method"."pluginId" as "pluginId", + "plugin_method"."name" as "methodName", + "plugin_method"."types" as "types", + "plugin_method"."hostFunctions" + from + "workflow_step" + inner join "plugin_method" on "plugin_method"."id" = "workflow_step"."pluginMethodId" + where + "workflow_step"."workflowId" = "workflow"."id" + and "workflow_step"."enabled" = $1 + ) as agg + ) as "steps" from "workflow" where - "ownerId" = $1 -order by - "createdAt" desc - --- WorkflowRepository.getWorkflowsByTrigger -select - * -from - "workflow" -where - "triggerType" = $1 - and "enabled" = $2 - --- WorkflowRepository.getWorkflowByOwnerAndTrigger -select - * -from - "workflow" -where - "ownerId" = $1 - and "triggerType" = $2 + "id" = $2 and "enabled" = $3 --- WorkflowRepository.deleteWorkflow +-- WorkflowRepository.delete delete from "workflow" where "id" = $1 - --- WorkflowRepository.getFilters -select - * -from - "workflow_filter" -where - "workflowId" = $1 -order by - "order" asc - --- WorkflowRepository.deleteFiltersByWorkflow -delete from "workflow_filter" -where - "workflowId" = $1 - --- WorkflowRepository.getActions -select - * -from - "workflow_action" -where - "workflowId" = $1 -order by - "order" asc diff --git a/server/src/repositories/album.repository.ts b/server/src/repositories/album.repository.ts index a910673c62..724788fa74 100644 --- a/server/src/repositories/album.repository.ts +++ b/server/src/repositories/album.repository.ts @@ -13,7 +13,7 @@ import { jsonArrayFrom, jsonObjectFrom } from 'kysely/helpers/postgres'; import { InjectKysely } from 'nestjs-kysely'; import { columns } from 'src/database'; import { Chunked, ChunkedArray, ChunkedSet, DummyValue, GenerateSql } from 'src/decorators'; -import { AlbumUserCreateDto } from 'src/dtos/album.dto'; +import { AlbumUserCreateDto, MapAlbumDto } from 'src/dtos/album.dto'; import { AlbumUserRole } from 'src/enum'; import { DB } from 'src/schema'; import { AlbumTable } from 'src/schema/tables/album.table'; @@ -183,98 +183,53 @@ export class AlbumRepository { ); } - @GenerateSql({ params: [DummyValue.UUID] }) - getOwned(ownerId: string) { + private buildAlbumBaseQuery(ownerId: string, { isOwned, isShared }: { isOwned?: boolean; isShared?: boolean }) { return this.db .selectFrom('album') - .selectAll('album') .innerJoin('album_user', (join) => - join - .onRef('album_user.albumId', '=', 'album.id') - .on('album_user.userId', '=', ownerId) - .on('album_user.role', '=', sql.lit(AlbumUserRole.Owner)), + join.onRef('album_user.albumId', '=', 'album.id').on('album_user.userId', '=', ownerId), ) .where('album.deletedAt', 'is', null) + .$if(isOwned === true, (qb) => qb.where('album_user.role', '=', sql.lit(AlbumUserRole.Owner))) + .$if(isOwned === false, (qb) => qb.where('album_user.role', '!=', sql.lit(AlbumUserRole.Owner))) + .$if(isShared !== undefined, (qb) => + qb.where((eb) => { + const isSharedAlbum = eb.or([ + eb.exists( + eb + .selectFrom('album_user as au') + .whereRef('au.albumId', '=', 'album.id') + .where('au.role', '!=', sql.lit(AlbumUserRole.Owner)), + ), + eb.exists(eb.selectFrom('shared_link').whereRef('shared_link.albumId', '=', 'album.id')), + ]); + return isShared ? isSharedAlbum : eb.not(isSharedAlbum); + }), + ); + } + + @GenerateSql({ params: [DummyValue.UUID, { isOwned: true, isShared: true }] }) + getAll( + ownerId: string, + options: { id?: string; isOwned?: boolean; isShared?: boolean; name?: string } = {}, + ): Promise { + return this.buildAlbumBaseQuery(ownerId, options) + .selectAll('album') .select(withAlbumUsers(ownerId)) .select(withSharedLink) + .$if(!!options.id, (qb) => qb.where('album.id', '=', options.id!)) + .$if(!!options.name, (qb) => qb.where('album.albumName', '=', options.name!)) .orderBy('album.createdAt', 'desc') .execute(); } - /** - * Get albums shared with and shared by owner. - */ - @GenerateSql({ params: [DummyValue.UUID] }) - getShared(ownerId: string) { - return this.db - .selectFrom('album') - .selectAll('album') - .innerJoin( - (eb) => - eb - .selectFrom('album_user') - .select('album_user.albumId as id') - .where('album_user.userId', '=', ownerId) - .where( - 'album_user.albumId', - 'in', - eb - .selectFrom('album_user') - .select('album_user.albumId') - .where('album_user.role', '!=', sql.lit(AlbumUserRole.Owner)), - ) - .union( - eb - .selectFrom('shared_link') - .where('shared_link.userId', '=', ownerId) - .where('shared_link.albumId', 'is not', null) - .select('shared_link.albumId as id') - .$narrowType<{ id: NotNull }>(), - ) - .as('matching'), - (join) => join.onRef('matching.id', '=', 'album.id'), - ) - .innerJoin('album_user', (join) => - join.onRef('album_user.albumId', '=', 'album.id').on('album_user.role', '=', sql.lit(AlbumUserRole.Owner)), - ) - .where('album.deletedAt', 'is', null) - .select(withAlbumUsers(ownerId)) - .select(withSharedLink) - .orderBy('album.createdAt', 'desc') - .execute(); - } - - /** - * Get albums of owner that are _not_ shared - */ - @GenerateSql({ params: [DummyValue.UUID] }) - getNotShared(ownerId: string) { - return this.db - .selectFrom('album') - .selectAll('album') - .innerJoin('album_user', (join) => - join - .onRef('album_user.albumId', '=', 'album.id') - .on('album_user.userId', '=', ownerId) - .on('album_user.role', '=', sql.lit(AlbumUserRole.Owner)), - ) - .where('album.deletedAt', 'is', null) - .where(({ not, exists, selectFrom }) => - not( - exists( - selectFrom('album_user as au') - .whereRef('au.albumId', '=', 'album.id') - .where('au.role', '!=', sql.lit(AlbumUserRole.Owner)), - ), - ), - ) - .where(({ not, exists, selectFrom }) => - not(exists(selectFrom('shared_link').whereRef('shared_link.albumId', '=', 'album.id'))), - ) - .select(withSharedLink) - .select(withAlbumUsers(ownerId)) + @GenerateSql({ params: [DummyValue.UUID, { isOwned: true, isShared: true }] }) + async getAllIds(ownerId: string, options: { isOwned?: boolean; isShared?: boolean } = {}): Promise { + const rows = await this.buildAlbumBaseQuery(ownerId, options) + .select('album.id') .orderBy('album.createdAt', 'desc') .execute(); + return rows.map((r) => r.id); } async restoreAll(userId: string): Promise { diff --git a/server/src/repositories/asset.repository.ts b/server/src/repositories/asset.repository.ts index 0b706cacf9..5488f747ad 100644 --- a/server/src/repositories/asset.repository.ts +++ b/server/src/repositories/asset.repository.ts @@ -17,7 +17,7 @@ import { InjectKysely } from 'nestjs-kysely'; import { LockableProperty, Stack } from 'src/database'; import { Chunked, ChunkedArray, DummyValue, GenerateSql } from 'src/decorators'; import { AuthDto } from 'src/dtos/auth.dto'; -import { AssetFileType, AssetOrder, AssetStatus, AssetType, AssetVisibility } from 'src/enum'; +import { AssetFileType, AssetOrder, AssetOrderBy, AssetStatus, AssetType, AssetVisibility } from 'src/enum'; import { DB } from 'src/schema'; import { AssetAudioTable, AssetKeyframeTable, AssetVideoTable } from 'src/schema/tables/asset-av.table'; import { AssetExifTable } from 'src/schema/tables/asset-exif.table'; @@ -89,6 +89,7 @@ interface AssetBuilderOptions { export interface TimeBucketOptions extends AssetBuilderOptions { order?: AssetOrder; + orderBy?: AssetOrderBy; } export interface TimeBucketItem { @@ -711,7 +712,7 @@ export class AssetRepository { .with('asset', (qb) => qb .selectFrom('asset') - .select(truncatedDate().as('timeBucket')) + .select(truncatedDate(options.orderBy).as('timeBucket')) .$if(!!options.isTrashed, (qb) => qb.where('asset.status', '!=', AssetStatus.Deleted)) .where('asset.deletedAt', options.isTrashed ? 'is not' : 'is', null) .$if(!!options.bbox, (qb) => { @@ -783,9 +784,8 @@ export class AssetRepository { 'asset.ownerId', 'asset.status', sql`asset."fileCreatedAt" at time zone 'utc'`.as('fileCreatedAt'), + sql`asset."createdAt" at time zone 'utc'`.as('createdAt'), eb.fn('encode', ['asset.thumbhash', sql.lit('base64')]).as('thumbhash'), - 'asset_exif.city', - 'asset_exif.country', 'asset_exif.projectionType', eb.fn .coalesce( @@ -799,6 +799,9 @@ export class AssetRepository { ) .as('ratio'), ]) + .$if(!auth.sharedLink || auth.sharedLink.showExif, (qb) => + qb.select(['asset_exif.city', 'asset_exif.country']), + ) .$if(!!options.withCoordinates, (qb) => qb.select(['asset_exif.latitude', 'asset_exif.longitude'])) .where('asset.deletedAt', options.isTrashed ? 'is not' : 'is', null) .$if(options.visibility == undefined, withDefaultVisibility) @@ -815,7 +818,7 @@ export class AssetRepository { return withBoundingBox(withBoundingCircle, bbox); }) - .where(truncatedDate(), '=', timeBucket.replace(/^[+-]/, '')) + .where(truncatedDate(options.orderBy), '=', timeBucket.replace(/^[+-]/, '')) .$if(!!options.albumId, (qb) => qb.where((eb) => eb.exists( @@ -861,15 +864,18 @@ export class AssetRepository { ) .$if(!!options.isTrashed, (qb) => qb.where('asset.status', '!=', AssetStatus.Deleted)) .$if(!!options.tagId, (qb) => withTagId(qb, options.tagId!)) - .orderBy(sql`(asset."localDateTime" AT TIME ZONE 'UTC')::date`, order) + .orderBy( + options.orderBy == AssetOrderBy.CreatedAt + ? sql`"createdAt"` + : sql`(asset."localDateTime" AT TIME ZONE 'UTC')::date`, + order, + ) .orderBy('asset.fileCreatedAt', order), ) .with('agg', (qb) => qb .selectFrom('cte') .select((eb) => [ - eb.fn.coalesce(eb.fn('array_agg', ['city']), sql.lit('{}')).as('city'), - eb.fn.coalesce(eb.fn('array_agg', ['country']), sql.lit('{}')).as('country'), eb.fn.coalesce(eb.fn('array_agg', ['duration']), sql.lit('{}')).as('duration'), eb.fn.coalesce(eb.fn('array_agg', ['id']), sql.lit('{}')).as('id'), eb.fn.coalesce(eb.fn('array_agg', ['visibility']), sql.lit('{}')).as('visibility'), @@ -880,12 +886,19 @@ export class AssetRepository { eb.fn.coalesce(eb.fn('array_agg', ['livePhotoVideoId']), sql.lit('{}')).as('livePhotoVideoId'), eb.fn.coalesce(eb.fn('array_agg', ['fileCreatedAt']), sql.lit('{}')).as('fileCreatedAt'), eb.fn.coalesce(eb.fn('array_agg', ['localOffsetHours']), sql.lit('{}')).as('localOffsetHours'), + eb.fn.coalesce(eb.fn('array_agg', ['createdAt']), sql.lit('{}')).as('createdAt'), eb.fn.coalesce(eb.fn('array_agg', ['ownerId']), sql.lit('{}')).as('ownerId'), eb.fn.coalesce(eb.fn('array_agg', ['projectionType']), sql.lit('{}')).as('projectionType'), eb.fn.coalesce(eb.fn('array_agg', ['ratio']), sql.lit('{}')).as('ratio'), eb.fn.coalesce(eb.fn('array_agg', ['status']), sql.lit('{}')).as('status'), eb.fn.coalesce(eb.fn('array_agg', ['thumbhash']), sql.lit('{}')).as('thumbhash'), ]) + .$if(!auth.sharedLink || auth.sharedLink.showExif, (qb) => + qb.select((eb) => [ + eb.fn.coalesce(eb.fn('array_agg', ['city']), sql.lit('{}')).as('city'), + eb.fn.coalesce(eb.fn('array_agg', ['country']), sql.lit('{}')).as('country'), + ]), + ) .$if(!!options.withCoordinates, (qb) => qb.select((eb) => [ eb.fn.coalesce(eb.fn('array_agg', ['latitude']), sql.lit('{}')).as('latitude'), @@ -929,6 +942,22 @@ export class AssetRepository { return { fieldName: 'exifInfo.city', items }; } + @GenerateSql({ params: [DummyValue.UUID, 12] }) + async getRecentlyCreatedAssetIds(ownerId: string, maxAssets: number) { + const items = await this.db + .selectFrom('asset') + .select(['id as data', 'createdAt as value']) + .where('ownerId', '=', asUuid(ownerId)) + .where('asset.visibility', '=', AssetVisibility.Timeline) + .where('type', '=', AssetType.Image) + .where('deletedAt', 'is', null) + .orderBy('value', 'desc') + .limit(maxAssets) + .execute(); + + return { fieldName: 'createdAt', items }; + } + async upsertFile( file: Pick< Insertable, diff --git a/server/src/repositories/config.repository.ts b/server/src/repositories/config.repository.ts index ddbdb2a856..f88deca09d 100644 --- a/server/src/repositories/config.repository.ts +++ b/server/src/repositories/config.repository.ts @@ -9,7 +9,7 @@ import { CLS_ID, ClsModuleOptions } from 'nestjs-cls'; import { OpenTelemetryModuleOptions } from 'nestjs-otel/lib/interfaces'; import { readFileSync } from 'node:fs'; import { join } from 'node:path'; -import { citiesFile, excludePaths, IWorker } from 'src/constants'; +import { citiesFile, IWorker } from 'src/constants'; import { Telemetry } from 'src/decorators'; import { EnvSchema } from 'src/dtos/env.dto'; import { @@ -328,10 +328,6 @@ const getEnv = (): EnvData => { otel: { metrics: { hostMetrics: telemetries.has(ImmichTelemetry.Host), - apiMetrics: { - enable: telemetries.has(ImmichTelemetry.Api), - ignoreRoutes: excludePaths, - }, }, }, @@ -350,7 +346,7 @@ const getEnv = (): EnvData => { root: folders.web, indexHtml: join(folders.web, 'index.html'), }, - corePlugin: join(buildFolder, 'corePlugin'), + corePlugin: join(buildFolder, 'plugins', 'immich-plugin-core'), }, setup: { diff --git a/server/src/repositories/database.repository.ts b/server/src/repositories/database.repository.ts index a86e929ef4..a4e58c52ec 100644 --- a/server/src/repositories/database.repository.ts +++ b/server/src/repositories/database.repository.ts @@ -24,7 +24,7 @@ import { DB } from 'src/schema'; import { immich_uuid_v7 } from 'src/schema/functions'; import { ExtensionVersion, VectorExtension } from 'src/types'; import { vectorIndexQuery } from 'src/utils/database'; -import { isValidInteger } from 'src/validation'; +import z from 'zod'; export let cachedVectorExtension: VectorExtension | undefined; export async function getVectorExtension(runner: Kysely): Promise { @@ -274,6 +274,7 @@ export class DatabaseRepository { columns: { ignoreExtra: true }, functions: { ignoreExtra: false }, parameters: { ignoreExtra: true }, + extensions: { ignoreExtra: true }, }); return drift; @@ -291,7 +292,13 @@ export class DatabaseRepository { `.execute(this.db); const dimSize = rows[0]?.dimsize; - if (!isValidInteger(dimSize, { min: 1, max: 2 ** 16 })) { + if ( + !z + .int() + .min(1) + .max(2 ** 16) + .safeParse(dimSize).success + ) { this.logger.warn(`Could not retrieve dimension size of column '${column}' in table '${table}', assuming 512`); return 512; } @@ -299,7 +306,13 @@ export class DatabaseRepository { } async setDimensionSize(dimSize: number): Promise { - if (!isValidInteger(dimSize, { min: 1, max: 2 ** 16 })) { + if ( + !z + .int() + .min(1) + .max(2 ** 16) + .safeParse(dimSize).success + ) { throw new Error(`Invalid CLIP dimension size: ${dimSize}`); } diff --git a/server/src/repositories/event.repository.ts b/server/src/repositories/event.repository.ts index 713828cd95..b4d968599b 100644 --- a/server/src/repositories/event.repository.ts +++ b/server/src/repositories/event.repository.ts @@ -9,7 +9,7 @@ import { AuthDto } from 'src/dtos/auth.dto'; import { ImmichWorker, JobStatus, MetadataKey, QueueName, UserAvatarColor, UserStatus } from 'src/enum'; import { ConfigRepository } from 'src/repositories/config.repository'; import { LoggingRepository } from 'src/repositories/logging.repository'; -import { JobItem, JobSource } from 'src/types'; +import { JobItem, JobSource, UploadFile } from 'src/types'; type EmitHandlers = Partial<{ [T in EmitEvent]: Array> }>; @@ -42,7 +42,7 @@ type EventMap = { AlbumInvite: [{ id: string; userId: string; senderName: string }]; // asset events - AssetCreate: [{ asset: Asset }]; + AssetCreate: [{ asset: Asset; file: UploadFile }]; AssetTag: [{ assetId: string }]; AssetUntag: [{ assetId: string }]; AssetHide: [{ assetId: string; userId: string }]; @@ -92,6 +92,14 @@ type EventMap = { AuthChangePassword: [{ userId: string; currentSessionId?: string; invalidateSessions?: boolean }]; + // hls streaming events + HlsSegmentRequest: [{ sessionId: string; assetId: string; variantIndex: number; segmentIndex: number }]; + HlsSegmentResult: [{ sessionId: string; variantIndex: number; segmentIndex: number; error?: string }]; + HlsHeartbeat: [{ sessionId: string; variantIndex?: number; segmentIndex?: number }]; + HlsSessionRequest: [{ sessionId: string; assetId: string; ownerId: string }]; + HlsSessionResult: [{ sessionId: string; error?: string }]; + HlsSessionEnd: [{ sessionId: string }]; + // websocket events WebsocketConnect: [{ userId: string }]; }; diff --git a/server/src/repositories/job.repository.ts b/server/src/repositories/job.repository.ts index a94e5aa9f6..5bb5276db7 100644 --- a/server/src/repositories/job.repository.ts +++ b/server/src/repositories/job.repository.ts @@ -171,8 +171,8 @@ export class JobRepository { options: this.getJobOptions(item) || undefined, } as JobItem & { data: any; options: JobsOptions | undefined }; - if (job.options?.jobId) { - // need to use add() instead of addBulk() for jobId deduplication + if (job.options?.jobId || job.options?.deduplication) { + // need to use add() instead of addBulk() for jobId/deduplication to take effect promises.push(this.getQueue(queueName).add(item.name, item.data, job.options)); } else { itemsByQueue[queueName] = itemsByQueue[queueName] || []; @@ -230,10 +230,13 @@ export class JobRepository { return { priority: 1 }; } case JobName.FacialRecognitionQueueAll: { - return { jobId: JobName.FacialRecognitionQueueAll }; + return { deduplication: { id: JobName.FacialRecognitionQueueAll } }; } case JobName.VersionCheck: { - return { jobId: JobName.VersionCheck }; + return { deduplication: { id: JobName.VersionCheck } }; + } + case JobName.DatabaseBackup: { + return { deduplication: { id: JobName.DatabaseBackup } }; } default: { return null; diff --git a/server/src/repositories/logging.repository.ts b/server/src/repositories/logging.repository.ts index 39867b14d0..c1df648f09 100644 --- a/server/src/repositories/logging.repository.ts +++ b/server/src/repositories/logging.repository.ts @@ -119,6 +119,10 @@ export class LoggingRepository { logLevels = level ? LOG_LEVELS.slice(LOG_LEVELS.indexOf(level)) : []; } + getLogLevel(): LogLevel { + return logLevels[0] || LogLevel.Fatal; + } + verbose(message: string, ...details: LogDetails) { this.handleMessage(LogLevel.Verbose, message, details); } diff --git a/server/src/repositories/machine-learning.repository.ts b/server/src/repositories/machine-learning.repository.ts index 49778b5193..6792e8ecf4 100644 --- a/server/src/repositories/machine-learning.repository.ts +++ b/server/src/repositories/machine-learning.repository.ts @@ -132,7 +132,7 @@ export class MachineLearningRepository { private async check(url: string) { let healthy = false; try { - const response = await fetch(new URL('/ping', url), { + const response = await fetch(new URL('ping', url), { signal: AbortSignal.timeout(this.config.availabilityChecks.timeout), }); if (response.ok) { @@ -170,7 +170,7 @@ export class MachineLearningRepository { ...this.config.urls.filter((url) => !this.isHealthy(url)), ]) { try { - const response = await fetch(new URL('/predict', url), { method: 'POST', body: formData }); + const response = await fetch(new URL('predict', url), { method: 'POST', body: formData }); if (response.ok) { this.setHealthy(url, true); return response.json(); diff --git a/server/src/repositories/media.repository.spec.ts b/server/src/repositories/media.repository.spec.ts index a5380852ee..e8106c0ff9 100644 --- a/server/src/repositories/media.repository.spec.ts +++ b/server/src/repositories/media.repository.spec.ts @@ -71,7 +71,7 @@ describe(MediaRepository.name, () => { describe('applyEdits (single actions)', () => { it('should apply crop edit correctly', async () => { - const result = await sut['applyEdits']( + const result = sut['applyEdits']( sharp({ create: { width: 1000, @@ -98,7 +98,7 @@ describe(MediaRepository.name, () => { expect(metadata.height).toBe(300); }); it('should apply rotate edit correctly', async () => { - const result = await sut['applyEdits']( + const result = sut['applyEdits']( sharp({ create: { width: 500, @@ -123,7 +123,7 @@ describe(MediaRepository.name, () => { }); it('should apply mirror edit correctly', async () => { - const resultHorizontal = await sut['applyEdits'](sharp(await buildTestQuadImage()), [ + const resultHorizontal = sut['applyEdits'](sharp(await buildTestQuadImage()), [ { action: AssetEditAction.Mirror, parameters: { @@ -142,7 +142,7 @@ describe(MediaRepository.name, () => { expect(await getPixelColor(bufferHorizontal, 10, 990)).toEqual({ r: 255, g: 255, b: 0 }); expect(await getPixelColor(bufferHorizontal, 990, 990)).toEqual({ r: 0, g: 0, b: 255 }); - const resultVertical = await sut['applyEdits'](sharp(await buildTestQuadImage()), [ + const resultVertical = sut['applyEdits'](sharp(await buildTestQuadImage()), [ { action: AssetEditAction.Mirror, parameters: { @@ -170,7 +170,7 @@ describe(MediaRepository.name, () => { describe('applyEdits (multiple sequential edits)', () => { it('should apply horizontal mirror then vertical mirror (equivalent to 180ยฐ rotation)', async () => { const imageBuffer = await buildTestQuadImage(); - const result = await sut['applyEdits'](sharp(imageBuffer), [ + const result = sut['applyEdits'](sharp(imageBuffer), [ { action: AssetEditAction.Mirror, parameters: { axis: MirrorAxis.Horizontal } }, { action: AssetEditAction.Mirror, parameters: { axis: MirrorAxis.Vertical } }, ]); @@ -188,7 +188,7 @@ describe(MediaRepository.name, () => { it('should apply rotate 90ยฐ then horizontal mirror', async () => { const imageBuffer = await buildTestQuadImage(); - const result = await sut['applyEdits'](sharp(imageBuffer), [ + const result = sut['applyEdits'](sharp(imageBuffer), [ { action: AssetEditAction.Rotate, parameters: { angle: 90 } }, { action: AssetEditAction.Mirror, parameters: { axis: MirrorAxis.Horizontal } }, ]); @@ -206,7 +206,7 @@ describe(MediaRepository.name, () => { it('should apply 180ยฐ rotation', async () => { const imageBuffer = await buildTestQuadImage(); - const result = await sut['applyEdits'](sharp(imageBuffer), [ + const result = sut['applyEdits'](sharp(imageBuffer), [ { action: AssetEditAction.Rotate, parameters: { angle: 180 } }, ]); @@ -223,7 +223,7 @@ describe(MediaRepository.name, () => { it('should apply 270ยฐ rotations', async () => { const imageBuffer = await buildTestQuadImage(); - const result = await sut['applyEdits'](sharp(imageBuffer), [ + const result = sut['applyEdits'](sharp(imageBuffer), [ { action: AssetEditAction.Rotate, parameters: { angle: 270 } }, ]); @@ -240,7 +240,7 @@ describe(MediaRepository.name, () => { it('should apply crop then rotate 90ยฐ', async () => { const imageBuffer = await buildTestQuadImage(); - const result = await sut['applyEdits'](sharp(imageBuffer), [ + const result = sut['applyEdits'](sharp(imageBuffer), [ { action: AssetEditAction.Crop, parameters: { x: 0, y: 0, width: 1000, height: 500 } }, { action: AssetEditAction.Rotate, parameters: { angle: 90 } }, ]); @@ -256,7 +256,7 @@ describe(MediaRepository.name, () => { it('should apply rotate 90ยฐ then crop', async () => { const imageBuffer = await buildTestQuadImage(); - const result = await sut['applyEdits'](sharp(imageBuffer), [ + const result = sut['applyEdits'](sharp(imageBuffer), [ { action: AssetEditAction.Crop, parameters: { x: 0, y: 0, width: 500, height: 1000 } }, { action: AssetEditAction.Rotate, parameters: { angle: 90 } }, ]); @@ -272,7 +272,7 @@ describe(MediaRepository.name, () => { it('should apply vertical mirror then horizontal mirror then rotate 90ยฐ', async () => { const imageBuffer = await buildTestQuadImage(); - const result = await sut['applyEdits'](sharp(imageBuffer), [ + const result = sut['applyEdits'](sharp(imageBuffer), [ { action: AssetEditAction.Mirror, parameters: { axis: MirrorAxis.Vertical } }, { action: AssetEditAction.Mirror, parameters: { axis: MirrorAxis.Horizontal } }, { action: AssetEditAction.Rotate, parameters: { angle: 90 } }, @@ -291,7 +291,7 @@ describe(MediaRepository.name, () => { it('should apply crop to single quadrant then mirror', async () => { const imageBuffer = await buildTestQuadImage(); - const result = await sut['applyEdits'](sharp(imageBuffer), [ + const result = sut['applyEdits'](sharp(imageBuffer), [ { action: AssetEditAction.Crop, parameters: { x: 0, y: 0, width: 500, height: 500 } }, { action: AssetEditAction.Mirror, parameters: { axis: MirrorAxis.Horizontal } }, ]); @@ -309,7 +309,7 @@ describe(MediaRepository.name, () => { it('should apply all operations: crop, rotate, mirror', async () => { const imageBuffer = await buildTestQuadImage(); - const result = await sut['applyEdits'](sharp(imageBuffer), [ + const result = sut['applyEdits'](sharp(imageBuffer), [ { action: AssetEditAction.Crop, parameters: { x: 0, y: 0, width: 500, height: 1000 } }, { action: AssetEditAction.Rotate, parameters: { angle: 90 } }, { action: AssetEditAction.Mirror, parameters: { axis: MirrorAxis.Horizontal } }, diff --git a/server/src/repositories/media.repository.ts b/server/src/repositories/media.repository.ts index ce81e7752b..c2ec95636a 100644 --- a/server/src/repositories/media.repository.ts +++ b/server/src/repositories/media.repository.ts @@ -77,34 +77,20 @@ export class MediaRepository { * @returns ExtractResult if succeeded, or null if failed */ async extract(input: string): Promise { - try { - const buffer = await exiftool.extractBinaryTagToBuffer('JpgFromRaw2', input); - return { buffer, format: RawExtractedFormat.Jpeg }; - } catch (error: any) { - this.logger.debug(`Could not extract JpgFromRaw2 buffer from image, trying JPEG from RAW next: ${error}`); - } - - try { - const buffer = await exiftool.extractBinaryTagToBuffer('JpgFromRaw', input); - return { buffer, format: RawExtractedFormat.Jpeg }; - } catch (error: any) { - this.logger.debug(`Could not extract JPEG buffer from image, trying PreviewJXL next: ${error}`); - } - - try { - const buffer = await exiftool.extractBinaryTagToBuffer('PreviewJXL', input); - return { buffer, format: RawExtractedFormat.Jxl }; - } catch (error: any) { - this.logger.debug(`Could not extract PreviewJXL buffer from image, trying PreviewImage next: ${error}`); - } - - try { - const buffer = await exiftool.extractBinaryTagToBuffer('PreviewImage', input); - return { buffer, format: RawExtractedFormat.Jpeg }; - } catch (error: any) { - this.logger.debug(`Could not extract preview buffer from image: ${error}`); - return null; + for (const { tag, format } of [ + { tag: 'JpgFromRaw2', format: RawExtractedFormat.Jpeg }, + { tag: 'JpgFromRaw', format: RawExtractedFormat.Jpeg }, + { tag: 'PreviewJXL', format: RawExtractedFormat.Jxl }, + { tag: 'PreviewImage', format: RawExtractedFormat.Jpeg }, + ]) { + try { + const buffer = await exiftool.extractBinaryTagToBuffer(tag, input); + return { buffer, format }; + } catch (error: any) { + this.logger.debug(`Could not extract ${tag} buffer from image: ${error}`); + } } + return null; } async writeExif(tags: Partial, output: string): Promise { @@ -162,49 +148,45 @@ export class MediaRepository { } } - async decodeImage(input: string | Buffer, options: DecodeToBufferOptions) { - const pipeline = await this.getImageDecodingPipeline(input, options); - return pipeline.raw().toBuffer({ resolveWithObject: true }); + decodeImage(input: string | Buffer, options: DecodeToBufferOptions) { + return this.getImageDecodingPipeline(input, options).raw().toBuffer({ resolveWithObject: true }); } - private async applyEdits(pipeline: sharp.Sharp, edits: AssetEditActionItem[]): Promise { - const affineEditOperations = edits.filter((edit) => edit.action !== 'crop'); - const matrix = createAffineMatrix(affineEditOperations); - + private applyEdits(pipeline: sharp.Sharp, edits: AssetEditActionItem[]): sharp.Sharp { const crop = edits.find((edit) => edit.action === 'crop'); - const dimensions = await pipeline.metadata(); - if (crop) { pipeline = pipeline.extract({ - left: crop ? Math.round(crop.parameters.x) : 0, - top: crop ? Math.round(crop.parameters.y) : 0, - width: crop ? Math.round(crop.parameters.width) : dimensions.width || 0, - height: crop ? Math.round(crop.parameters.height) : dimensions.height || 0, + left: Math.round(crop.parameters.x), + top: Math.round(crop.parameters.y), + width: Math.round(crop.parameters.width), + height: Math.round(crop.parameters.height), }); } - const { a, b, c, d } = matrix; - pipeline = pipeline.affine([ - [a, b], - [c, d], - ]); + const affineEditOperations = edits.filter((edit) => edit.action !== 'crop'); + if (affineEditOperations.length > 0) { + const { a, b, c, d } = createAffineMatrix(affineEditOperations); + pipeline = pipeline.affine([ + [a, b], + [c, d], + ]); + } return pipeline; } async generateThumbnail(input: string | Buffer, options: GenerateThumbnailOptions, output: string): Promise { - const pipeline = await this.getImageDecodingPipeline(input, options); - const decoded = pipeline.toFormat(options.format, { - quality: options.quality, - // this is default in libvips (except the threshold is 90), but we need to set it manually in sharp - chromaSubsampling: options.quality >= 80 ? '4:4:4' : '4:2:0', - progressive: options.progressive, - }); - - await decoded.toFile(output); + await this.getImageDecodingPipeline(input, options) + .toFormat(options.format, { + quality: options.quality, + // this is default in libvips (except the threshold is 90), but we need to set it manually in sharp + chromaSubsampling: options.quality >= 80 ? '4:4:4' : '4:2:0', + progressive: options.progressive, + }) + .toFile(output); } - private async getImageDecodingPipeline(input: string | Buffer, options: DecodeToBufferOptions) { + private getImageDecodingPipeline(input: string | Buffer, options: DecodeToBufferOptions) { let pipeline = sharp(input, { // some invalid images can still be processed by sharp, but we want to fail on them by default to avoid crashes failOn: options.processInvalidImages ? 'none' : 'error', @@ -228,7 +210,7 @@ export class MediaRepository { } if (options.edits && options.edits.length > 0) { - pipeline = await this.applyEdits(pipeline, options.edits); + pipeline = this.applyEdits(pipeline, options.edits); } if (options.size !== undefined) { @@ -238,19 +220,18 @@ export class MediaRepository { } async generateThumbhash(input: string | Buffer, options: GenerateThumbhashOptions): Promise { - const [{ rgbaToThumbHash }, decodingPipeline] = await Promise.all([ - import('thumbhash'), - this.getImageDecodingPipeline(input, { - colorspace: options.colorspace, - processInvalidImages: options.processInvalidImages, - raw: options.raw, - edits: options.edits, - }), - ]); + const { rgbaToThumbHash } = await import('thumbhash'); - const pipeline = decodingPipeline.resize(100, 100, { fit: 'inside', withoutEnlargement: true }).raw().ensureAlpha(); - - const { data, info } = await pipeline.toBuffer({ resolveWithObject: true }); + const { data, info } = await this.getImageDecodingPipeline(input, { + colorspace: options.colorspace, + processInvalidImages: options.processInvalidImages, + raw: options.raw, + edits: options.edits, + }) + .resize(100, 100, { fit: 'inside', withoutEnlargement: true }) + .raw() + .ensureAlpha() + .toBuffer({ resolveWithObject: true }); return Buffer.from(rgbaToThumbHash(info.width, info.height, data)); } @@ -274,23 +255,23 @@ export class MediaRepository { index: stream.index, height, width: dar ? Math.round(height * dar) : this.parseInt(stream.width), - codecName: stream.codec_name === 'h265' ? 'hevc' : stream.codec_name, - profile: this.parseVideoProfile(stream.codec_name, stream.profile as string | undefined), + codecName: stream.codec_name === 'h265' ? 'hevc' : (stream.codec_name ?? null), + profile: this.parseVideoProfile(stream.codec_name, stream.profile as string | undefined) ?? null, level: this.parseOptionalInt(stream.level), frameCount: this.parseInt(options?.countFrames ? stream.nb_read_packets : stream.nb_frames), frameRate: this.parseFrameRate(stream.avg_frame_rate ?? stream.r_frame_rate), - timeBase: this.parseRational(stream.time_base)?.den, + timeBase: this.parseRational(stream.time_base)?.den ?? null, rotation: this.parseInt(stream.rotation), bitrate: this.parseInt(stream.bit_rate), pixelFormat: stream.pix_fmt || 'yuv420p', colorPrimaries: this.parseEnum(ColorPrimaries, stream.color_primaries) ?? ColorPrimaries.Unknown, colorMatrix: this.parseEnum(ColorMatrix, stream.color_space) ?? ColorMatrix.Unknown, colorTransfer: this.parseEnum(ColorTransfer, stream.color_transfer) ?? ColorTransfer.Unknown, - dvProfile: this.parseOptionalInt(stream.dv_profile) as DvProfile | undefined, + dvProfile: this.parseOptionalInt(stream.dv_profile) as DvProfile | null, dvLevel: this.parseOptionalInt(stream.dv_level), - dvBlSignalCompatibilityId: this.parseOptionalInt(stream.dv_bl_signal_compatibility_id) as - | DvSignalCompatibility - | undefined, + dvBlSignalCompatibilityId: this.parseOptionalInt( + stream.dv_bl_signal_compatibility_id, + ) as DvSignalCompatibility | null, }; }), audioStreams: results.streams @@ -298,9 +279,9 @@ export class MediaRepository { .sort((a, b) => this.compareStreams(a, b)) .map((stream) => ({ index: stream.index, - codecName: stream.codec_name, + codecName: stream.codec_name ?? null, profile: - stream.codec_name === 'aac' ? this.parseEnum(AacProfile, stream.profile as string | undefined) : undefined, + stream.codec_name === 'aac' ? this.parseEnum(AacProfile, stream.profile as string | undefined) : null, bitrate: this.parseInt(stream.bit_rate), })), }; @@ -449,29 +430,29 @@ export class MediaRepository { return Number.parseFloat(value as string) || 0; } - private parseOptionalInt(value: string | number | undefined): number | undefined { + private parseOptionalInt(value: string | number | undefined): number | null { const parsed = Number.parseInt(value as string); - return Number.isNaN(parsed) ? undefined : parsed; + return Number.isNaN(parsed) ? null : parsed; } private parseEnum>(enumObj: E, value?: string) { - return value ? (enumObj[pascalCase(value)] as Extract | undefined) : undefined; + return value ? ((enumObj[pascalCase(value)] as Extract | undefined) ?? null) : null; } /** Parse a rational like "60000/1001" or "1/600" into `{ num, den }`. */ - private parseRational(value: string | undefined): { num: number; den: number } | undefined { - if (!value) { - return; - } - const [num, den = 1] = value.split('/').map(Number); - if (num && den) { - return { num, den }; + private parseRational(value: string | undefined): { num: number; den: number } | null { + if (value) { + const [num, den = 1] = value.split('/').map(Number); + if (num && den) { + return { num, den }; + } } + return null; } - private parseFrameRate(value: string | undefined): number | undefined { + private parseFrameRate(value: string | undefined): number | null { const r = this.parseRational(value); - return r ? r.num / r.den : undefined; + return r ? r.num / r.den : null; } private getDar(dar: string | undefined): number { @@ -498,6 +479,7 @@ export class MediaRepository { return this.parseEnum(Av1Profile, profile); } } + return null; } private compareStreams(a: FfprobeStream, b: FfprobeStream): number { @@ -508,18 +490,43 @@ export class MediaRepository { return this.parseInt(b.bit_rate) - this.parseInt(a.bit_rate); } + /* Ported from https://code.ffmpeg.org/FFmpeg/FFmpeg/src/commit/5c44245878e235ae64fe87fb9877644856d33d1d/fftools/ffmpeg_filter.c + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright (c) FFmpeg authors and contributors โ€” https://ffmpeg.org/ + * Modifications: TS port operating on probe-derived packet metadata rather than decoded AVFrames. */ private cfrOutputFrames(packets: { pts: number; duration: number }[], slotsPerTick: number) { - // Packets may be out of PTS order due to B-frames packets.sort((a, b) => a.pts - b.pts); const firstPts = packets[0].pts; let outputFrames = 0; let nextPts = 0; + const history = [0, 0, 0]; for (const pkt of packets) { - const delta = (pkt.pts - firstPts) * slotsPerTick - nextPts + pkt.duration * slotsPerTick; - const nb = delta < -1.1 ? 0 : delta > 1.1 ? Math.round(delta) : 1; + const syncIpts = (pkt.pts - firstPts) * slotsPerTick; + const duration = pkt.duration * slotsPerTick; + let delta0 = syncIpts - nextPts; + const delta = delta0 + duration; + + if (delta0 < 0 && delta > 0) { + delta0 = 0; + } + + let nb = 1; + let nbPrev = 0; + if (delta < -1.1) { + nb = 0; + } else if (delta > 1.1) { + nb = Math.round(delta); + if (delta0 > 1.1) { + nbPrev = Math.round(delta0 - 0.6); + } + } outputFrames += nb; nextPts += nb; + history[2] = history[1]; + history[1] = history[0]; + history[0] = nbPrev; } - return outputFrames; + const median = history.sort((a, b) => a - b)[1]; + return outputFrames + median; } } diff --git a/server/src/repositories/memory.repository.ts b/server/src/repositories/memory.repository.ts index e62c083839..09aa5ad880 100644 --- a/server/src/repositories/memory.repository.ts +++ b/server/src/repositories/memory.repository.ts @@ -66,9 +66,21 @@ export class MemoryRepository implements IBulkAsset { .selectAll('asset') .innerJoin('memory_asset', 'asset.id', 'memory_asset.assetId') .whereRef('memory_asset.memoriesId', '=', 'memory.id') - .orderBy('asset.fileCreatedAt', 'asc') .where('asset.visibility', '=', sql.lit(AssetVisibility.Timeline)) - .where('asset.deletedAt', 'is', null), + .where('asset.deletedAt', 'is', null) + .where((eb) => + eb.not( + eb.exists( + eb + .selectFrom('asset_face') + .innerJoin('person', 'person.id', 'asset_face.personId') + .select((eb) => eb.val(1).as('one')) + .whereRef('asset_face.assetId', '=', 'asset.id') + .where('person.isHidden', '=', true), + ), + ), + ) + .orderBy('asset.fileCreatedAt', 'asc'), ).as('assets'), ) .selectAll('memory') diff --git a/server/src/repositories/metadata.repository.ts b/server/src/repositories/metadata.repository.ts index 57c688cac2..188cc016f1 100644 --- a/server/src/repositories/metadata.repository.ts +++ b/server/src/repositories/metadata.repository.ts @@ -1,5 +1,5 @@ import { Injectable } from '@nestjs/common'; -import { BinaryField, DefaultReadTaskOptions, ExifTool, Tags } from 'exiftool-vendored'; +import { BinaryField, DefaultReadTaskOptions, ExifTool, ReadTaskOptions, Tags } from 'exiftool-vendored'; import geotz from 'geo-tz'; import { LoggingRepository } from 'src/repositories/logging.repository'; import { mimeTypes } from 'src/utils/mime-types'; @@ -89,7 +89,7 @@ export class MetadataRepository { geoTz: (lat, lon) => geotz.find(lat, lon)[0], geolocation: true, // Enable exiftool LFS to parse metadata for files larger than 2GB. - readArgs: ['-api', 'largefilesupport=1'], + readArgs: ['-api', 'largefilesupport=1', '--ICC_Profile:DeviceManufacturer', '--ICC_Profile:DeviceModelName'], writeArgs: ['-api', 'largefilesupport=1', '-overwrite_original'], taskTimeoutMillis: 2 * 60 * 1000, }); @@ -107,8 +107,8 @@ export class MetadataRepository { } readTags(path: string): Promise { - const args = mimeTypes.isVideo(path) ? ['-ee'] : []; - return this.exiftool.read(path, { readArgs: args }).catch((error) => { + const options: ReadTaskOptions | undefined = mimeTypes.isVideo(path) ? { readArgs: ['-ee'] } : undefined; + return this.exiftool.read(path, options).catch((error) => { this.logger.warn(`Error reading exif data (${path}): ${error}\n${error?.stack}`); return {}; }) as Promise; diff --git a/server/src/repositories/person.repository.ts b/server/src/repositories/person.repository.ts index 2a9f822e94..0db03a18c7 100644 --- a/server/src/repositories/person.repository.ts +++ b/server/src/repositories/person.repository.ts @@ -4,7 +4,7 @@ import { jsonObjectFrom } from 'kysely/helpers/postgres'; import { InjectKysely } from 'nestjs-kysely'; import { AssetFace } from 'src/database'; import { Chunked, ChunkedArray, DummyValue, GenerateSql } from 'src/decorators'; -import { AssetFileType, AssetVisibility, SourceType } from 'src/enum'; +import { AssetFileType, AssetVisibility, SourceType, UserMetadataKey } from 'src/enum'; import { DB } from 'src/schema'; import { AssetFaceTable } from 'src/schema/tables/asset-face.table'; import { FaceSearchTable } from 'src/schema/tables/face-search.table'; @@ -13,7 +13,6 @@ import { dummy, removeUndefinedKeys, withFilePath } from 'src/utils/database'; import { paginationHelper, PaginationOptions } from 'src/utils/pagination'; export interface PersonSearchOptions { - minimumFaceCount: number; withHidden: boolean; closestFaceAssetId?: string; } @@ -168,7 +167,17 @@ export class PersonRepository { .having((eb) => eb.or([ eb('person.name', '!=', ''), - eb((innerEb) => innerEb.fn.count('asset_face.assetId'), '>=', options?.minimumFaceCount || 1), + eb( + (innerEb) => innerEb.fn.count('asset_face.assetId'), + '>=', + sql`COALESCE( + (SELECT value -> 'people' ->> 'minimumFaces' + FROM user_metadata + WHERE "userId" = ${userId} + AND key = ${sql.lit(UserMetadataKey.Preferences)}), + '3' + )::int `, + ), ]), ) .groupBy('person.id') diff --git a/server/src/repositories/plugin.repository.ts b/server/src/repositories/plugin.repository.ts index 6217237947..43006c6aa6 100644 --- a/server/src/repositories/plugin.repository.ts +++ b/server/src/repositories/plugin.repository.ts @@ -1,176 +1,262 @@ +import { CallContext, Plugin as ExtismPlugin, newPlugin } from '@extism/extism'; import { Injectable } from '@nestjs/common'; -import { Kysely } from 'kysely'; +import { createPool, Pool } from 'generic-pool'; +import { Insertable, Kysely } from 'kysely'; import { jsonArrayFrom } from 'kysely/helpers/postgres'; import { InjectKysely } from 'nestjs-kysely'; -import { readdir } from 'node:fs/promises'; import { columns } from 'src/database'; import { DummyValue, GenerateSql } from 'src/decorators'; -import { PluginManifestDto } from 'src/dtos/plugin-manifest.dto'; +import { PluginMethodSearchDto, PluginSearchDto } from 'src/dtos/plugin.dto'; +import { LogLevel, WorkflowType } from 'src/enum'; +import { LoggingRepository } from 'src/repositories/logging.repository'; import { DB } from 'src/schema'; +import { PluginMethodTable } from 'src/schema/tables/plugin-method.table'; +import { PluginTable } from 'src/schema/tables/plugin.table'; + +type PluginMethod = { pluginKey: string; methodName: string }; +type PluginLoad = { key: string; label: string; wasmBytes: Buffer }; + +export type PluginHostFunction = (callContext: CallContext, input: bigint) => Promise | bigint; +export type PluginLoadOptions = { + runInWorker?: boolean; + functions?: Record; +}; + +export type PluginMethodSearchResponse = { + id: string; + name: string; + pluginName: string; + types: WorkflowType[]; +}; + +const levels = { + [LogLevel.Verbose]: 'trace', + [LogLevel.Debug]: 'debug', + [LogLevel.Log]: 'info', + [LogLevel.Warn]: 'warn', + [LogLevel.Error]: 'error', + [LogLevel.Fatal]: 'error', +} as const; + +const asExtismLogLevel = (logLevel: LogLevel) => levels[logLevel] || 'info'; @Injectable() export class PluginRepository { - constructor(@InjectKysely() private db: Kysely) {} + private pluginMap: Map }> = new Map(); - /** - * Loads a plugin from a validated manifest file in a transaction. - * This ensures all plugin, filter, and action operations are atomic. - * @param manifest The validated plugin manifest - * @param basePath The base directory path where the plugin is located - */ - async loadPlugin(manifest: PluginManifestDto, basePath: string) { - return this.db.transaction().execute(async (tx) => { - // Upsert the plugin - const plugin = await tx - .insertInto('plugin') - .values({ - name: manifest.name, - title: manifest.title, - description: manifest.description, - author: manifest.author, - version: manifest.version, - wasmPath: `${basePath}/${manifest.wasm.path}`, - }) - .onConflict((oc) => - oc.column('name').doUpdateSet({ - title: manifest.title, - description: manifest.description, - author: manifest.author, - version: manifest.version, - wasmPath: `${basePath}/${manifest.wasm.path}`, - }), - ) - .returningAll() - .executeTakeFirstOrThrow(); - - const filters = manifest.filters - ? await tx - .insertInto('plugin_filter') - .values( - manifest.filters.map((filter) => ({ - pluginId: plugin.id, - methodName: filter.methodName, - title: filter.title, - description: filter.description, - supportedContexts: filter.supportedContexts, - schema: filter.schema, - })), - ) - .onConflict((oc) => - oc.column('methodName').doUpdateSet((eb) => ({ - pluginId: eb.ref('excluded.pluginId'), - title: eb.ref('excluded.title'), - description: eb.ref('excluded.description'), - supportedContexts: eb.ref('excluded.supportedContexts'), - schema: eb.ref('excluded.schema'), - })), - ) - .returningAll() - .execute() - : []; - - const actions = manifest.actions - ? await tx - .insertInto('plugin_action') - .values( - manifest.actions.map((action) => ({ - pluginId: plugin.id, - methodName: action.methodName, - title: action.title, - description: action.description, - supportedContexts: action.supportedContexts, - schema: action.schema, - })), - ) - .onConflict((oc) => - oc.column('methodName').doUpdateSet((eb) => ({ - pluginId: eb.ref('excluded.pluginId'), - title: eb.ref('excluded.title'), - description: eb.ref('excluded.description'), - supportedContexts: eb.ref('excluded.supportedContexts'), - schema: eb.ref('excluded.schema'), - })), - ) - .returningAll() - .execute() - : []; - - return { plugin, filters, actions }; - }); - } - - async readDirectory(path: string) { - return readdir(path, { withFileTypes: true }); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - getPlugin(id: string) { - return this.db - .selectFrom('plugin') - .select((eb) => [ - ...columns.plugin, - jsonArrayFrom( - eb.selectFrom('plugin_filter').selectAll().whereRef('plugin_filter.pluginId', '=', 'plugin.id'), - ).as('filters'), - jsonArrayFrom( - eb.selectFrom('plugin_action').selectAll().whereRef('plugin_action.pluginId', '=', 'plugin.id'), - ).as('actions'), - ]) - .where('plugin.id', '=', id) - .executeTakeFirst(); - } - - @GenerateSql({ params: [DummyValue.STRING] }) - getPluginByName(name: string) { - return this.db - .selectFrom('plugin') - .select((eb) => [ - ...columns.plugin, - jsonArrayFrom( - eb.selectFrom('plugin_filter').selectAll().whereRef('plugin_filter.pluginId', '=', 'plugin.id'), - ).as('filters'), - jsonArrayFrom( - eb.selectFrom('plugin_action').selectAll().whereRef('plugin_action.pluginId', '=', 'plugin.id'), - ).as('actions'), - ]) - .where('plugin.name', '=', name) - .executeTakeFirst(); + constructor( + @InjectKysely() private db: Kysely, + private logger: LoggingRepository, + ) { + this.logger.setContext(PluginRepository.name); } @GenerateSql() - getAllPlugins() { + getForLoad() { return this.db .selectFrom('plugin') .select((eb) => [ - ...columns.plugin, + 'plugin.id', + 'plugin.name', + 'plugin.version', + 'plugin.wasmBytes', jsonArrayFrom( - eb.selectFrom('plugin_filter').selectAll().whereRef('plugin_filter.pluginId', '=', 'plugin.id'), - ).as('filters'), - jsonArrayFrom( - eb.selectFrom('plugin_action').selectAll().whereRef('plugin_action.pluginId', '=', 'plugin.id'), - ).as('actions'), + eb + .selectFrom('plugin_method') + .whereRef('plugin_method.pluginId', '=', 'plugin.id') + .select(['plugin_method.name', 'plugin_method.hostFunctions']), + ).as('methods'), ]) + .where('enabled', '=', true) + .execute(); + } + + private queryBuilder() { + return this.db.selectFrom('plugin').select((eb) => [ + 'plugin.id', + 'plugin.name', + 'plugin.title', + 'plugin.description', + 'plugin.author', + 'plugin.version', + 'plugin.createdAt', + 'plugin.updatedAt', + 'plugin.templates', + jsonArrayFrom( + eb + .selectFrom('plugin_method') + .select([...columns.pluginMethod, 'plugin.name as pluginName']) + .whereRef('plugin_method.pluginId', '=', 'plugin.id'), + ).as('methods'), + ]); + } + + @GenerateSql() + search(dto: PluginSearchDto = {}) { + return this.queryBuilder() + .$if(!!dto.id, (qb) => qb.where('plugin.id', '=', dto.id!)) + .$if(!!dto.name, (qb) => qb.where('plugin.name', '=', dto.name!)) + .$if(!!dto.title, (qb) => qb.where('plugin.title', '=', dto.title!)) + .$if(!!dto.description, (qb) => qb.where('plugin.description', '=', dto.description!)) + .$if(!!dto.version, (qb) => qb.where('plugin.version', '=', dto.version!)) .orderBy('plugin.name') .execute(); } - @GenerateSql({ params: [DummyValue.UUID] }) - getFilter(id: string) { - return this.db.selectFrom('plugin_filter').selectAll().where('id', '=', id).executeTakeFirst(); + @GenerateSql({ params: [DummyValue.STRING] }) + getByHash(hash: Buffer) { + return this.queryBuilder().where('plugin.sha256hash', '=', hash).executeTakeFirst(); + } + + @GenerateSql({ params: [DummyValue.STRING] }) + getByName(name: string) { + return this.queryBuilder().where('plugin.name', '=', name).executeTakeFirst(); } @GenerateSql({ params: [DummyValue.UUID] }) - getFiltersByPlugin(pluginId: string) { - return this.db.selectFrom('plugin_filter').selectAll().where('pluginId', '=', pluginId).execute(); + get(id: string) { + return this.queryBuilder().where('plugin.id', '=', id).executeTakeFirst(); } - @GenerateSql({ params: [DummyValue.UUID] }) - getAction(id: string) { - return this.db.selectFrom('plugin_action').selectAll().where('id', '=', id).executeTakeFirst(); + @GenerateSql() + getForValidation(): Promise { + return this.db + .selectFrom('plugin_method') + .innerJoin('plugin', 'plugin_method.pluginId', 'plugin.id') + .select(['plugin_method.id', 'plugin_method.name', 'plugin.name as pluginName', 'plugin_method.types']) + .execute(); } - @GenerateSql({ params: [DummyValue.UUID] }) - getActionsByPlugin(pluginId: string) { - return this.db.selectFrom('plugin_action').selectAll().where('pluginId', '=', pluginId).execute(); + @GenerateSql() + searchMethods(dto: PluginMethodSearchDto = {}) { + return this.db + .selectFrom('plugin_method') + .innerJoin('plugin', 'plugin.id', 'plugin_method.pluginId') + .select(['plugin.name as pluginName', 'plugin_method.pluginId', 'plugin_method.id', ...columns.pluginMethod]) + .$if(!!dto.id, (qb) => qb.where('plugin_method.id', '=', dto.id!)) + .$if(!!dto.name, (qb) => qb.where('plugin_method.name', '=', dto.name!)) + .$if(!!dto.title, (qb) => qb.where('plugin_method.title', '=', dto.title!)) + .$if(!!dto.type, (qb) => qb.where('plugin_method.types', '@>', [dto.type!])) + .$if(!!dto.description, (qb) => qb.where('plugin_method.description', '=', dto.description!)) + .$if(!!dto.pluginVersion, (qb) => qb.where('plugin.version', '=', dto.pluginVersion!)) + .$if(!!dto.pluginName, (qb) => qb.where('plugin.name', '=', dto.pluginName!)) + .orderBy('plugin_method.name') + .execute(); + } + + async upsert(dto: Insertable, initialMethods: Omit, 'pluginId'>[]) { + return this.db.transaction().execute(async (tx) => { + // Upsert the plugin + const plugin = await tx + .insertInto('plugin') + .values(dto) + .onConflict((oc) => + oc.columns(['name', 'version']).doUpdateSet((eb) => ({ + title: eb.ref('excluded.title'), + description: eb.ref('excluded.description'), + author: eb.ref('excluded.author'), + version: eb.ref('excluded.version'), + wasmBytes: eb.ref('excluded.wasmBytes'), + templates: eb.ref('excluded.templates'), + sha256hash: eb.ref('excluded.sha256hash'), + })), + ) + .returning(['id', 'name']) + .executeTakeFirstOrThrow(); + + // prune methods that no longer exist + if (initialMethods.length > 0) { + await tx + .deleteFrom('plugin_method') + .where('plugin_method.pluginId', '=', plugin.id) + .where( + 'name', + 'not in', + initialMethods.map((method) => method.name), + ) + .execute(); + } + + const methods = + initialMethods.length > 0 + ? await tx + .insertInto('plugin_method') + .values(initialMethods.map((method) => ({ ...method, pluginId: plugin.id }))) + .onConflict((oc) => + oc.columns(['pluginId', 'name']).doUpdateSet(({ ref }) => ({ + pluginId: ref('excluded.pluginId'), + name: ref('excluded.name'), + title: ref('excluded.title'), + description: ref('excluded.description'), + types: ref('excluded.types'), + hostFunctions: ref('excluded.hostFunctions'), + uiHints: ref('excluded.uiHints'), + schema: ref('excluded.schema'), + })), + ) + .returningAll() + .execute() + : []; + + return { ...plugin, methods }; + }); + } + + async load({ key, label, wasmBytes }: PluginLoad, { runInWorker, functions }: PluginLoadOptions) { + const data = new Uint8Array(wasmBytes.buffer, wasmBytes.byteOffset, wasmBytes.byteLength); + const logger = LoggingRepository.create(`Plugin:${label}`); + const pool = createPool( + { + create: () => + newPlugin( + { wasm: [{ data }] }, + { + useWasi: true, + runInWorker, + functions: { + 'extism:host/user': functions ?? {}, + }, + logger: { + trace: (message) => logger.verbose(message), + info: (message) => logger.log(message), + debug: (message) => logger.debug(message), + warn: (message) => logger.warn(message), + error: (message) => logger.error(message), + } as Console, + logLevel: asExtismLogLevel(logger.getLogLevel()), + }, + ), + destroy: (plugin) => plugin.close(), + }, + { min: 1, max: 5 }, + ); + + try { + await pool.ready(); + this.pluginMap.set(key, { pool, label }); + } catch (error: Error | any) { + throw new Error(`Unable to instantiate plugin: ${key}`, { cause: error }); + } + } + + async callMethod({ pluginKey, methodName }: PluginMethod, input: unknown) { + const item = this.pluginMap.get(pluginKey); + if (!item) { + throw new Error(`No loaded plugin found for ${pluginKey}`); + } + + const { pool, label } = item; + + try { + const plugin = await pool.acquire(); + try { + const result = await plugin.call(methodName, JSON.stringify(input)); + return (result ? result.json() : result) as T; + } finally { + await pool.release(plugin); + } + } catch (error: Error | any) { + throw new Error(`Plugin method call failed: ${label}#${methodName}`, { cause: error }); + } } } diff --git a/server/src/repositories/process.repository.ts b/server/src/repositories/process.repository.ts index 9d8cac1f40..928531408f 100644 --- a/server/src/repositories/process.repository.ts +++ b/server/src/repositories/process.repository.ts @@ -1,12 +1,10 @@ import { Injectable } from '@nestjs/common'; -import { ChildProcessWithoutNullStreams, fork, spawn, SpawnOptionsWithoutStdio } from 'node:child_process'; +import { fork, spawn, SpawnOptionsWithoutStdio } from 'node:child_process'; import { Duplex } from 'node:stream'; @Injectable() export class ProcessRepository { - spawn(command: string, args?: readonly string[], options?: SpawnOptionsWithoutStdio): ChildProcessWithoutNullStreams { - return spawn(command, args, options); - } + spawn = spawn; spawnDuplexStream(command: string, args?: readonly string[], options?: SpawnOptionsWithoutStdio): Duplex { let stdinClosed = false; diff --git a/server/src/repositories/search.repository.ts b/server/src/repositories/search.repository.ts index 6f03c80ce1..da3f31555a 100644 --- a/server/src/repositories/search.repository.ts +++ b/server/src/repositories/search.repository.ts @@ -8,7 +8,7 @@ import { DB } from 'src/schema'; import { AssetExifTable } from 'src/schema/tables/asset-exif.table'; import { anyUuid, searchAssetBuilder, withExifInner } from 'src/utils/database'; import { paginationHelper } from 'src/utils/pagination'; -import { isValidInteger } from 'src/validation'; +import z from 'zod'; export interface SearchAssetIdOptions { checksum?: Buffer; @@ -278,7 +278,7 @@ export class SearchRepository { ], }) searchSmart(pagination: SearchPaginationOptions, options: SmartSearchOptions) { - if (!isValidInteger(pagination.size, { min: 1, max: 1000 })) { + if (!z.int().min(1).max(1000).safeParse(pagination.size).success) { throw new Error(`Invalid value for 'size': ${pagination.size}`); } @@ -313,7 +313,7 @@ export class SearchRepository { ], }) searchFaces({ userIds, embedding, numResults, maxDistance, hasPerson, minBirthDate }: FaceEmbeddingSearch) { - if (!isValidInteger(numResults, { min: 1, max: 1000 })) { + if (!z.int().min(1).max(1000).safeParse(numResults).success) { throw new Error(`Invalid value for 'numResults': ${numResults}`); } diff --git a/server/src/repositories/server-info.repository.ts b/server/src/repositories/server-info.repository.ts index 85d26d6cfa..c8fb896281 100644 --- a/server/src/repositories/server-info.repository.ts +++ b/server/src/repositories/server-info.repository.ts @@ -4,6 +4,7 @@ import { exec as execCallback } from 'node:child_process'; import { readFile } from 'node:fs/promises'; import { promisify } from 'node:util'; import sharp from 'sharp'; +import { ReleaseChannel } from 'src/dtos/system-config.dto'; import { ConfigRepository } from 'src/repositories/config.repository'; import { LoggingRepository } from 'src/repositories/logging.repository'; @@ -64,10 +65,12 @@ export class ServerInfoRepository { this.logger.setContext(ServerInfoRepository.name); } - async getLatestRelease(): Promise { + async getLatestRelease(channel: ReleaseChannel): Promise { try { const { versionCheck } = this.configRepository.getEnv(); - const response = await fetch(versionCheck.url); + const url = new URL(versionCheck.url); + url.searchParams.append('channel', channel); + const response = await fetch(url); if (!response.ok) { throw new Error(`Version check request failed with status ${response.status}: ${await response.text()}`); diff --git a/server/src/repositories/storage.repository.ts b/server/src/repositories/storage.repository.ts index c7ba4ab6cc..9604372fbe 100644 --- a/server/src/repositories/storage.repository.ts +++ b/server/src/repositories/storage.repository.ts @@ -2,7 +2,16 @@ import { Injectable } from '@nestjs/common'; import archiver from 'archiver'; import chokidar, { ChokidarOptions } from 'chokidar'; import { escapePath, glob, globStream } from 'fast-glob'; -import { constants, createReadStream, createWriteStream, existsSync, mkdirSync, ReadOptionsWithBuffer } from 'node:fs'; +import { + constants, + createReadStream, + createWriteStream, + Dirent, + existsSync, + mkdirSync, + ReadOptionsWithBuffer, + watch, +} from 'node:fs'; import fs from 'node:fs/promises'; import path from 'node:path'; import { PassThrough, Readable, Writable } from 'node:stream'; @@ -50,6 +59,10 @@ export class StorageRepository { return fs.readdir(folder); } + readdirWithTypes(folder: string): Promise { + return fs.readdir(folder, { withFileTypes: true }); + } + copyFile(source: string, target: string) { return fs.copyFile(source, target); } @@ -117,17 +130,24 @@ export class StorageRepository { } async readFile(filepath: string, options?: ReadOptionsWithBuffer): Promise { - const file = await fs.open(filepath); - try { - const { buffer } = await file.read(options); - return buffer as Buffer; - } finally { - await file.close(); + // read a slice + if (options) { + const file = await fs.open(filepath); + try { + const { buffer } = await file.read(options); + return buffer as Buffer; + } finally { + await file.close(); + } } + + // read everything + return fs.readFile(filepath); } - async readTextFile(filepath: string): Promise { - return fs.readFile(filepath, 'utf8'); + async readJsonFile(filepath: string): Promise { + const file = await fs.readFile(filepath, 'utf8'); + return JSON.parse(file) as T; } async checkFileExists(filepath: string, mode = constants.F_OK): Promise { @@ -258,6 +278,8 @@ export class StorageRepository { return () => watcher.close(); } + watchDir = watch; // Native fs.watch without chokidar overhead + private asGlob(pathToCrawl: string): string { const escapedPath = escapePath(pathToCrawl).replaceAll('"', '["]').replaceAll("'", "[']").replaceAll('`', '[`]'); const extensions = `*{${mimeTypes.getSupportedFileExtensions().join(',')}}`; diff --git a/server/src/repositories/sync.repository.ts b/server/src/repositories/sync.repository.ts index b56f8bbd8e..e5832df8ee 100644 --- a/server/src/repositories/sync.repository.ts +++ b/server/src/repositories/sync.repository.ts @@ -597,7 +597,8 @@ class PartnerAssetsSync extends BaseSync { @GenerateSql({ params: [dummyBackfillOptions, DummyValue.UUID], stream: true }) getBackfill(options: SyncBackfillOptions, partnerId: string) { return this.backfillQuery('asset', options) - .select(columns.syncAsset) + .select(columns.syncPartnerAsset) + .select(sql.val(false).as('isFavorite')) .select('asset.updateId') .where('ownerId', '=', partnerId) .stream(); @@ -616,7 +617,8 @@ class PartnerAssetsSync extends BaseSync { @GenerateSql({ params: [dummyQueryOptions], stream: true }) getUpserts(options: SyncQueryOptions) { return this.upsertQuery('asset', options) - .select(columns.syncAsset) + .select(columns.syncPartnerAsset) + .select(sql.val(false).as('isFavorite')) .select('asset.updateId') .where('ownerId', 'in', (eb) => eb.selectFrom('partner').select(['sharedById']).where('sharedWithId', '=', options.userId), diff --git a/server/src/repositories/telemetry.repository.ts b/server/src/repositories/telemetry.repository.ts index d87c0acf5a..036a1f9fab 100644 --- a/server/src/repositories/telemetry.repository.ts +++ b/server/src/repositories/telemetry.repository.ts @@ -14,7 +14,7 @@ import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from '@opentelemetry/semantic import { snakeCase, startCase } from 'lodash'; import { MetricService } from 'nestjs-otel'; import { copyMetadataFromFunctionToFunction } from 'nestjs-otel/lib/opentelemetry.utils'; -import { serverVersion } from 'src/constants'; +import { excludePaths, serverVersion } from 'src/constants'; import { ImmichTelemetry, MetadataKey } from 'src/enum'; import { ConfigRepository } from 'src/repositories/config.repository'; import { LoggingRepository } from 'src/repositories/logging.repository'; @@ -60,6 +60,9 @@ export const bootstrapTelemetry = (port: number) => { if (instance) { throw new Error('OpenTelemetry SDK already started'); } + + const { telemetry } = new ConfigRepository().getEnv(); + instance = new NodeSDK({ resource: resourceFromAttributes({ [ATTR_SERVICE_NAME]: `immich`, @@ -68,7 +71,10 @@ export const bootstrapTelemetry = (port: number) => { metricReader: new PrometheusExporter({ port }), contextManager: new AsyncLocalStorageContextManager(), instrumentations: [ - new HttpInstrumentation(), + new HttpInstrumentation({ + enabled: telemetry.metrics.has(ImmichTelemetry.Api), + ignoreIncomingRequestHook: (request) => excludePaths.some((item) => request.url?.startsWith(item)), + }), new IORedisInstrumentation(), new NestInstrumentation(), new PgInstrumentation(), diff --git a/server/src/repositories/video-stream.repository.ts b/server/src/repositories/video-stream.repository.ts index e23ee4ca4c..43c5ef80f0 100644 --- a/server/src/repositories/video-stream.repository.ts +++ b/server/src/repositories/video-stream.repository.ts @@ -8,6 +8,7 @@ import { VideoStreamSessionTable, VideoStreamVariantTable, } from 'src/schema/tables/video-stream.table'; +import { withAudioStream, withVideoFormat, withVideoPackets, withVideoStream } from 'src/utils/database'; @Injectable() export class VideoStreamRepository { @@ -27,7 +28,12 @@ export class VideoStreamRepository { @GenerateSql({ params: [DummyValue.UUID] }) getSession(id: string) { - return this.db.selectFrom('video_stream_session').selectAll().where('id', '=', id).executeTakeFirst(); + return this.db + .selectFrom('video_stream_session') + .selectAll() + .where('id', '=', id) + .where('expiresAt', '>', new Date()) + .executeTakeFirst(); } @GenerateSql({ params: [DummyValue.UUID] }) @@ -47,7 +53,12 @@ export class VideoStreamRepository { @GenerateSql() getExpiredSessions() { - return this.db.selectFrom('video_stream_session').select(['id']).where('expiresAt', '<=', new Date()).execute(); + return this.db + .selectFrom('video_stream_session') + .innerJoin('asset', 'asset.id', 'video_stream_session.assetId') + .select(['video_stream_session.id', 'asset.ownerId']) + .where('video_stream_session.expiresAt', '<=', new Date()) + .execute(); } @GenerateSql({ params: [DummyValue.UUID, DummyValue.DATE] }) @@ -59,4 +70,50 @@ export class VideoStreamRepository { async deleteSession(id: string) { await this.db.deleteFrom('video_stream_session').where('id', '=', id).execute(); } + + @GenerateSql({ params: [DummyValue.UUID] }) + async getForMainPlaylist(id: string) { + return this.db + .selectFrom('asset') + .innerJoin('asset_exif', 'asset.id', 'asset_exif.assetId') + .where('asset.id', '=', id) + .innerJoin('asset_video', 'asset.id', 'asset_video.assetId') + .innerJoin('asset_keyframe', 'asset.id', 'asset_keyframe.assetId') + .select((eb) => withVideoStream(eb).$notNull().as('videoStream')) + .select((eb) => withVideoPackets(eb).$notNull().as('packets')) + .executeTakeFirst(); + } + + @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID] }) + async getForMediaPlaylist(id: string, sessionId: string) { + return this.db + .selectFrom('asset') + .innerJoin('asset_exif', 'asset.id', 'asset_exif.assetId') + .innerJoin('video_stream_session', 'asset.id', 'video_stream_session.assetId') + .where('asset.id', '=', id) + .where('video_stream_session.id', '=', sessionId) + .where('video_stream_session.expiresAt', '>', new Date()) + .innerJoin('asset_video', 'asset.id', 'asset_video.assetId') + .innerJoin('asset_keyframe', 'asset.id', 'asset_keyframe.assetId') + .select((eb) => withVideoStream(eb).$notNull().as('videoStream')) + .select((eb) => withVideoPackets(eb).$notNull().as('packets')) + .executeTakeFirst(); + } + + @GenerateSql({ params: [DummyValue.UUID] }) + async getForTranscoding(id: string) { + return this.db + .selectFrom('asset') + .innerJoin('asset_exif', 'asset.id', 'asset_exif.assetId') + .where('asset.id', '=', id) + .leftJoin('asset_audio', 'asset.id', 'asset_audio.assetId') + .innerJoin('asset_video', 'asset.id', 'asset_video.assetId') + .innerJoin('asset_keyframe', 'asset.id', 'asset_keyframe.assetId') + .select('asset.originalPath') + .select((eb) => withAudioStream(eb).as('audioStream')) + .select((eb) => withVideoStream(eb).$notNull().as('videoStream')) + .select((eb) => withVideoFormat(eb).$notNull().as('format')) + .select((eb) => withVideoPackets(eb).$notNull().as('packets')) + .executeTakeFirst(); + } } diff --git a/server/src/repositories/websocket.repository.ts b/server/src/repositories/websocket.repository.ts index b4a0fcc00a..d79e1563e3 100644 --- a/server/src/repositories/websocket.repository.ts +++ b/server/src/repositories/websocket.repository.ts @@ -10,13 +10,22 @@ import { Server, Socket } from 'socket.io'; import { AssetResponseDto } from 'src/dtos/asset-response.dto'; import { AuthDto } from 'src/dtos/auth.dto'; import { NotificationDto } from 'src/dtos/notification.dto'; -import { ReleaseNotification, ServerVersionResponseDto } from 'src/dtos/server.dto'; +import { ReleaseEventV1, ServerVersionResponseDto } from 'src/dtos/server.dto'; import { SyncAssetEditV1, SyncAssetExifV1, SyncAssetV2 } from 'src/dtos/sync.dto'; import { AppRestartEvent, ArgsOf, EventRepository } from 'src/repositories/event.repository'; import { LoggingRepository } from 'src/repositories/logging.repository'; import { handlePromiseError } from 'src/utils/misc'; -export const serverEvents = ['ConfigUpdate', 'AppRestart'] as const; +export const serverEvents = [ + 'ConfigUpdate', + 'AppRestart', + 'HlsSegmentRequest', + 'HlsSegmentResult', + 'HlsHeartbeat', + 'HlsSessionRequest', + 'HlsSessionResult', + 'HlsSessionEnd', +] as const; export type ServerEvents = (typeof serverEvents)[number]; export interface ClientEventMap { @@ -31,7 +40,7 @@ export interface ClientEventMap { on_person_thumbnail: [string]; on_server_version: [ServerVersionResponseDto]; on_config_update: []; - on_new_release: [ReleaseNotification]; + on_new_release: [ReleaseEventV1]; on_notification: [NotificationDto]; on_session_delete: [string]; diff --git a/server/src/repositories/workflow.repository.ts b/server/src/repositories/workflow.repository.ts index deaf2aa2fc..9ceef72a50 100644 --- a/server/src/repositories/workflow.repository.ts +++ b/server/src/repositories/workflow.repository.ts @@ -1,149 +1,185 @@ import { Injectable } from '@nestjs/common'; import { Insertable, Kysely, Updateable } from 'kysely'; +import { jsonArrayFrom, jsonObjectFrom } from 'kysely/helpers/postgres'; import { InjectKysely } from 'nestjs-kysely'; +import { columns } from 'src/database'; import { DummyValue, GenerateSql } from 'src/decorators'; -import { PluginTriggerType } from 'src/enum'; +import { WorkflowSearchDto } from 'src/dtos/workflow.dto'; import { DB } from 'src/schema'; -import { WorkflowActionTable, WorkflowFilterTable, WorkflowTable } from 'src/schema/tables/workflow.table'; +import { WorkflowStepTable } from 'src/schema/tables/workflow-step.table'; +import { WorkflowTable } from 'src/schema/tables/workflow.table'; + +export type WorkflowStepUpsert = Omit, 'workflowId' | 'order'>; @Injectable() export class WorkflowRepository { constructor(@InjectKysely() private db: Kysely) {} + private queryBuilder(db?: Kysely) { + return (db ?? this.db) + .selectFrom('workflow') + .select([ + 'workflow.id', + 'workflow.name', + 'workflow.description', + 'workflow.trigger', + 'workflow.enabled', + 'workflow.createdAt', + 'workflow.updatedAt', + ]) + .select((eb) => [ + jsonArrayFrom( + eb + .selectFrom('workflow_step') + .innerJoin('plugin_method', 'plugin_method.id', 'workflow_step.pluginMethodId') + .innerJoin('plugin', 'plugin.id', 'plugin_method.pluginId') + .whereRef('workflow.id', '=', 'workflow_step.workflowId') + .select([ + 'plugin.name as pluginName', + 'plugin_method.name as methodName', + 'workflow_step.config', + 'workflow_step.enabled', + ]), + ).as('steps'), + ]); + } + @GenerateSql({ params: [DummyValue.UUID] }) - getWorkflow(id: string) { + search(dto: WorkflowSearchDto & { userId?: string }) { + return this.queryBuilder() + .$if(!!dto.id, (qb) => qb.where('id', '=', dto.id!)) + .$if(!!dto.userId, (qb) => qb.where('ownerId', '=', dto.userId!)) + .$if(!!dto.trigger, (qb) => qb.where('trigger', '=', dto.trigger!)) + .$if(dto.enabled !== undefined, (qb) => qb.where('enabled', '=', dto.enabled!)) + .orderBy('createdAt', 'desc') + .execute(); + } + + @GenerateSql({ params: [DummyValue.UUID] }) + get(id: string) { + return this.queryBuilder().where('id', '=', id).executeTakeFirst(); + } + + @GenerateSql({ params: [DummyValue.UUID] }) + getForWorkflowRun(id: string) { return this.db .selectFrom('workflow') - .selectAll() + .select(['workflow.id', 'workflow.name', 'workflow.trigger']) + .select((eb) => [ + jsonArrayFrom( + eb + .selectFrom('workflow_step') + .innerJoin('plugin_method', 'plugin_method.id', 'workflow_step.pluginMethodId') + .whereRef('workflow_step.workflowId', '=', 'workflow.id') + .where('workflow_step.enabled', '=', true) + .select([ + 'workflow_step.id', + 'workflow_step.config', + 'plugin_method.pluginId as pluginId', + 'plugin_method.name as methodName', + 'plugin_method.types as types', + 'plugin_method.hostFunctions', + ]), + ).as('steps'), + ]) .where('id', '=', id) - .orderBy('createdAt', 'desc') + .where('enabled', '=', true) .executeTakeFirst(); } - @GenerateSql({ params: [DummyValue.UUID] }) - getWorkflowsByOwner(ownerId: string) { - return this.db - .selectFrom('workflow') - .selectAll() - .where('ownerId', '=', ownerId) - .orderBy('createdAt', 'desc') - .execute(); - } - - @GenerateSql({ params: [PluginTriggerType.AssetCreate] }) - getWorkflowsByTrigger(type: PluginTriggerType) { - return this.db - .selectFrom('workflow') - .selectAll() - .where('triggerType', '=', type) - .where('enabled', '=', true) - .execute(); - } - - @GenerateSql({ params: [DummyValue.UUID, PluginTriggerType.AssetCreate] }) - getWorkflowByOwnerAndTrigger(ownerId: string, type: PluginTriggerType) { - return this.db - .selectFrom('workflow') - .selectAll() - .where('ownerId', '=', ownerId) - .where('triggerType', '=', type) - .where('enabled', '=', true) - .execute(); - } - - async createWorkflow( - workflow: Insertable, - filters: Insertable[], - actions: Insertable[], - ) { - return await this.db.transaction().execute(async (tx) => { - const createdWorkflow = await tx.insertInto('workflow').values(workflow).returningAll().executeTakeFirstOrThrow(); - - if (filters.length > 0) { - const newFilters = filters.map((filter) => ({ - ...filter, - workflowId: createdWorkflow.id, - })); - - await tx.insertInto('workflow_filter').values(newFilters).execute(); - } - - if (actions.length > 0) { - const newActions = actions.map((action) => ({ - ...action, - workflowId: createdWorkflow.id, - })); - await tx.insertInto('workflow_action').values(newActions).execute(); - } - - return createdWorkflow; + create(dto: Insertable, steps?: WorkflowStepUpsert[]) { + return this.db.transaction().execute(async (tx) => { + const { id } = await tx.insertInto('workflow').values(dto).returning(['id']).executeTakeFirstOrThrow(); + return this.replaceAndReturn(tx, id, steps); }); } - async updateWorkflow( - id: string, - workflow: Updateable, - filters: Insertable[] | undefined, - actions: Insertable[] | undefined, - ) { - return await this.db.transaction().execute(async (trx) => { - if (Object.keys(workflow).length > 0) { - await trx.updateTable('workflow').set(workflow).where('id', '=', id).execute(); + update(id: string, dto: Updateable, steps?: WorkflowStepUpsert[]) { + return this.db.transaction().execute(async (tx) => { + if (Object.values(dto).some((prop) => prop !== undefined)) { + await tx.updateTable('workflow').set(dto).where('id', '=', id).executeTakeFirstOrThrow(); } - - if (filters !== undefined) { - await trx.deleteFrom('workflow_filter').where('workflowId', '=', id).execute(); - if (filters.length > 0) { - const filtersWithWorkflowId = filters.map((filter) => ({ - ...filter, - workflowId: id, - })); - await trx.insertInto('workflow_filter').values(filtersWithWorkflowId).execute(); - } - } - - if (actions !== undefined) { - await trx.deleteFrom('workflow_action').where('workflowId', '=', id).execute(); - if (actions.length > 0) { - const actionsWithWorkflowId = actions.map((action) => ({ - ...action, - workflowId: id, - })); - await trx.insertInto('workflow_action').values(actionsWithWorkflowId).execute(); - } - } - - return await trx.selectFrom('workflow').selectAll().where('id', '=', id).executeTakeFirstOrThrow(); + return this.replaceAndReturn(tx, id, steps); }); } + async updateStep(id: string, dto: Updateable) { + await this.db.updateTable('workflow_step').where('workflow_step.id', '=', id).set(dto).execute(); + } + + private async replaceAndReturn(tx: Kysely, workflowId: string, steps?: WorkflowStepUpsert[]) { + if (steps) { + await tx.deleteFrom('workflow_step').where('workflowId', '=', workflowId).execute(); + if (steps.length > 0) { + await tx + .insertInto('workflow_step') + .values( + steps.map((step, i) => ({ + workflowId, + enabled: step.enabled ?? true, + pluginMethodId: step.pluginMethodId, + config: step.config, + order: i, + })), + ) + .returningAll() + .execute(); + } + } + + return this.queryBuilder(tx).where('id', '=', workflowId).executeTakeFirstOrThrow(); + } + @GenerateSql({ params: [DummyValue.UUID] }) - async deleteWorkflow(id: string) { + async delete(id: string) { await this.db.deleteFrom('workflow').where('id', '=', id).execute(); } - @GenerateSql({ params: [DummyValue.UUID] }) - getFilters(workflowId: string) { + getForAssetV1(assetId: string) { return this.db - .selectFrom('workflow_filter') - .selectAll() - .where('workflowId', '=', workflowId) - .orderBy('order', 'asc') - .execute(); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - async deleteFiltersByWorkflow(workflowId: string) { - await this.db.deleteFrom('workflow_filter').where('workflowId', '=', workflowId).execute(); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - getActions(workflowId: string) { - return this.db - .selectFrom('workflow_action') - .selectAll() - .where('workflowId', '=', workflowId) - .orderBy('order', 'asc') - .execute(); + .selectFrom('asset') + .leftJoin('asset_exif', 'asset_exif.assetId', 'asset.id') + .select((eb) => [ + ...columns.workflowAssetV1, + jsonObjectFrom( + eb + .selectFrom('asset_exif') + .select([ + 'asset_exif.make', + 'asset_exif.model', + 'asset_exif.orientation', + 'asset_exif.dateTimeOriginal', + 'asset_exif.modifyDate', + 'asset_exif.exifImageWidth', + 'asset_exif.exifImageHeight', + 'asset_exif.fileSizeInByte', + 'asset_exif.lensModel', + 'asset_exif.fNumber', + 'asset_exif.focalLength', + 'asset_exif.iso', + 'asset_exif.latitude', + 'asset_exif.longitude', + 'asset_exif.city', + 'asset_exif.state', + 'asset_exif.country', + 'asset_exif.description', + 'asset_exif.fps', + 'asset_exif.exposureTime', + 'asset_exif.livePhotoCID', + 'asset_exif.timeZone', + 'asset_exif.projectionType', + 'asset_exif.profileDescription', + 'asset_exif.colorspace', + 'asset_exif.bitsPerSample', + 'asset_exif.autoStackId', + 'asset_exif.rating', + 'asset_exif.tags', + 'asset_exif.updatedAt', + ]) + .whereRef('asset_exif.assetId', '=', 'asset.id'), + ).as('exifInfo'), + ]) + .where('id', '=', assetId) + .executeTakeFirstOrThrow(); } } diff --git a/server/src/schema/enums.ts b/server/src/schema/enums.ts index 73f8133441..ecf559c39d 100644 --- a/server/src/schema/enums.ts +++ b/server/src/schema/enums.ts @@ -1,12 +1,5 @@ import { registerEnum } from '@immich/sql-tools'; -import { - AlbumUserRole, - AssetStatus, - AssetVisibility, - ChecksumAlgorithm, - SourceType, - VideoSegmentCodec, -} from 'src/enum'; +import { AlbumUserRole, AssetStatus, AssetVisibility, ChecksumAlgorithm, SourceType, VideoCodec } from 'src/enum'; export const album_user_role_enum = registerEnum({ name: 'album_user_role_enum', @@ -35,5 +28,5 @@ export const asset_checksum_algorithm_enum = registerEnum({ export const video_stream_variant_codec_enum = registerEnum({ name: 'video_stream_variant_codec_enum', - values: Object.values(VideoSegmentCodec), + values: [VideoCodec.Av1, VideoCodec.Hevc, VideoCodec.H264], }); diff --git a/server/src/schema/index.ts b/server/src/schema/index.ts index 9b1b6ec7d2..5bbe7cf4f4 100644 --- a/server/src/schema/index.ts +++ b/server/src/schema/index.ts @@ -62,7 +62,8 @@ import { PartnerAuditTable } from 'src/schema/tables/partner-audit.table'; import { PartnerTable } from 'src/schema/tables/partner.table'; import { PersonAuditTable } from 'src/schema/tables/person-audit.table'; import { PersonTable } from 'src/schema/tables/person.table'; -import { PluginActionTable, PluginFilterTable, PluginTable } from 'src/schema/tables/plugin.table'; +import { PluginMethodTable } from 'src/schema/tables/plugin-method.table'; +import { PluginTable } from 'src/schema/tables/plugin.table'; import { SessionTable } from 'src/schema/tables/session.table'; import { SharedLinkAssetTable } from 'src/schema/tables/shared-link-asset.table'; import { SharedLinkTable } from 'src/schema/tables/shared-link.table'; @@ -84,7 +85,8 @@ import { VideoStreamSessionTable, VideoStreamVariantTable, } from 'src/schema/tables/video-stream.table'; -import { WorkflowActionTable, WorkflowFilterTable, WorkflowTable } from 'src/schema/tables/workflow.table'; +import { WorkflowStepTable } from 'src/schema/tables/workflow-step.table'; +import { WorkflowTable } from 'src/schema/tables/workflow.table'; @Extensions(['uuid-ossp', 'unaccent', 'cube', 'earthdistance', 'pg_trgm', 'plpgsql']) @Database({ name: 'immich' }) @@ -146,11 +148,9 @@ export class ImmichDatabase { VideoStreamVariantTable, VideoStreamSegmentTable, PluginTable, - PluginFilterTable, - PluginActionTable, + PluginMethodTable, WorkflowTable, - WorkflowFilterTable, - WorkflowActionTable, + WorkflowStepTable, ]; functions = [ @@ -269,10 +269,8 @@ export interface DB { video_stream_segment: VideoStreamSegmentTable; plugin: PluginTable; - plugin_filter: PluginFilterTable; - plugin_action: PluginActionTable; + plugin_method: PluginMethodTable; workflow: WorkflowTable; - workflow_filter: WorkflowFilterTable; - workflow_action: WorkflowActionTable; + workflow_step: WorkflowStepTable; } diff --git a/server/src/schema/migrations/1777897107000-PartnerAssetSyncReset.ts b/server/src/schema/migrations/1777897107000-PartnerAssetSyncReset.ts new file mode 100644 index 0000000000..cd75895755 --- /dev/null +++ b/server/src/schema/migrations/1777897107000-PartnerAssetSyncReset.ts @@ -0,0 +1,10 @@ +import { Kysely, sql } from 'kysely'; + +export async function up(db: Kysely): Promise { + // isFavorite was incorrectly included in partner asset sync on server <=2.X.X + await sql`DELETE FROM session_sync_checkpoint WHERE type in ('PartnerAssetV1', 'PartnerAssetBackfillV1')`.execute(db); +} + +export async function down(): Promise { + // Not implemented +} diff --git a/server/src/schema/migrations/1778614946174-UpdateWorkflowTables.ts b/server/src/schema/migrations/1778614946174-UpdateWorkflowTables.ts new file mode 100644 index 0000000000..087c9a55ab --- /dev/null +++ b/server/src/schema/migrations/1778614946174-UpdateWorkflowTables.ts @@ -0,0 +1,83 @@ +import { Kysely, sql } from 'kysely'; + +export async function up(db: Kysely): Promise { + // take #2... + await sql`DROP TABLE "workflow_action";`.execute(db); + await sql`DROP TABLE "workflow_filter";`.execute(db); + await sql`DROP TABLE "workflow";`.execute(db); + await sql`DROP TABLE "plugin_action";`.execute(db); + await sql`DROP TABLE "plugin_filter";`.execute(db); + await sql`DROP TABLE "plugin";`.execute(db); + + await sql`CREATE TABLE "plugin" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "enabled" boolean NOT NULL DEFAULT true, + "name" character varying NOT NULL, + "version" character varying NOT NULL, + "title" character varying NOT NULL, + "description" character varying NOT NULL, + "author" character varying NOT NULL, + "wasmBytes" bytea NOT NULL, + "createdAt" timestamp with time zone NOT NULL DEFAULT now(), + "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), + CONSTRAINT "plugin_name_version_uq" UNIQUE ("name", "version"), + CONSTRAINT "plugin_name_uq" UNIQUE ("name"), + CONSTRAINT "plugin_pkey" PRIMARY KEY ("id") +);`.execute(db); + await sql`CREATE INDEX "plugin_name_idx" ON "plugin" ("name");`.execute(db); + await sql`CREATE TABLE "plugin_method" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "pluginId" uuid NOT NULL, + "name" character varying NOT NULL, + "title" character varying NOT NULL, + "description" character varying NOT NULL, + "types" character varying[] NOT NULL, + "hostFunctions" boolean NOT NULL DEFAULT false, + "uiHints" character varying[] NOT NULL DEFAULT '{}', + "schema" jsonb, + CONSTRAINT "plugin_method_pluginId_fkey" FOREIGN KEY ("pluginId") REFERENCES "plugin" ("id") ON UPDATE CASCADE ON DELETE CASCADE, + CONSTRAINT "plugin_method_pluginId_name_uq" UNIQUE ("pluginId", "name"), + CONSTRAINT "plugin_method_pkey" PRIMARY KEY ("id") +);`.execute(db); + await sql`CREATE INDEX "plugin_method_pluginId_idx" ON "plugin_method" ("pluginId");`.execute(db); + await sql`CREATE TABLE "workflow" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "ownerId" uuid NOT NULL, + "trigger" character varying NOT NULL, + "name" character varying, + "description" character varying, + "createdAt" timestamp with time zone NOT NULL DEFAULT now(), + "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), + "updateId" uuid NOT NULL DEFAULT immich_uuid_v7(), + "enabled" boolean NOT NULL DEFAULT true, + CONSTRAINT "workflow_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "user" ("id") ON UPDATE CASCADE ON DELETE CASCADE, + CONSTRAINT "workflow_pkey" PRIMARY KEY ("id") +);`.execute(db); + await sql`CREATE INDEX "workflow_ownerId_idx" ON "workflow" ("ownerId");`.execute(db); + await sql`CREATE OR REPLACE TRIGGER "workflow_updatedAt" + BEFORE UPDATE ON "workflow" + FOR EACH ROW + EXECUTE FUNCTION updated_at();`.execute(db); + await sql`CREATE TABLE "workflow_step" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "enabled" boolean NOT NULL DEFAULT true, + "workflowId" uuid NOT NULL, + "pluginMethodId" uuid NOT NULL, + "config" jsonb, + "order" integer NOT NULL, + CONSTRAINT "workflow_step_workflowId_fkey" FOREIGN KEY ("workflowId") REFERENCES "workflow" ("id") ON UPDATE CASCADE ON DELETE CASCADE, + CONSTRAINT "workflow_step_pluginMethodId_fkey" FOREIGN KEY ("pluginMethodId") REFERENCES "plugin_method" ("id") ON UPDATE CASCADE ON DELETE CASCADE, + CONSTRAINT "workflow_step_pkey" PRIMARY KEY ("id") +);`.execute(db); + await sql`CREATE INDEX "workflow_step_workflowId_idx" ON "workflow_step" ("workflowId");`.execute(db); + await sql`CREATE INDEX "workflow_step_pluginMethodId_idx" ON "workflow_step" ("pluginMethodId");`.execute(db); + await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_workflow_updatedAt', '{"type":"trigger","name":"workflow_updatedAt","sql":"CREATE OR REPLACE TRIGGER \\"workflow_updatedAt\\"\\n BEFORE UPDATE ON \\"workflow\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();"}'::jsonb);`.execute( + db, + ); + await sql`DELETE FROM "migration_overrides" WHERE "name" = 'index_plugin_filter_supportedContexts_idx';`.execute(db); + await sql`DELETE FROM "migration_overrides" WHERE "name" = 'index_plugin_action_supportedContexts_idx';`.execute(db); +} + +export async function down(): Promise { + // not supported +} diff --git a/server/src/schema/migrations/1779806699547-AddPluginTemplates.ts b/server/src/schema/migrations/1779806699547-AddPluginTemplates.ts new file mode 100644 index 0000000000..9e0e75a443 --- /dev/null +++ b/server/src/schema/migrations/1779806699547-AddPluginTemplates.ts @@ -0,0 +1,13 @@ +import { Kysely, sql } from 'kysely'; + +export async function up(db: Kysely): Promise { + await sql`ALTER TABLE "plugin" ADD "templates" jsonb NOT NULL DEFAULT '[]';`.execute(db); + await sql`ALTER TABLE "plugin" ADD "sha256hash" bytea NOT NULL DEFAULT decode('20464b37ad726d03d878d38d873c40a52d1fdfb754feda956ebb464afd689e2f', 'hex');`.execute(db); + await sql`ALTER TABLE "plugin" ALTER COLUMN "sha256hash" DROP DEFAULT;`.execute(db); + await sql`ALTER TABLE "plugin" ALTER COLUMN "templates" DROP DEFAULT;`.execute(db); +} + +export async function down(db: Kysely): Promise { + await sql`ALTER TABLE "plugin" DROP COLUMN "templates";`.execute(db); + await sql`ALTER TABLE "plugin" DROP COLUMN "sha256hash";`.execute(db); +} diff --git a/server/src/schema/migrations/1780435471692-DeleteMismatchedAssetFaces.ts b/server/src/schema/migrations/1780435471692-DeleteMismatchedAssetFaces.ts new file mode 100644 index 0000000000..d0387c4038 --- /dev/null +++ b/server/src/schema/migrations/1780435471692-DeleteMismatchedAssetFaces.ts @@ -0,0 +1,16 @@ +import { Kysely, sql } from 'kysely'; + +export async function up(db: Kysely): Promise { + // Delete unauthorized cross-owner asset faces + await sql` + DELETE FROM asset_face + USING person, asset + WHERE asset_face."personId" = person.id + AND asset_face."assetId" = asset.id + AND person."ownerId" != asset."ownerId" + `.execute(db); +} + +export async function down(): Promise { + // Not implemented: the deleted rows were unauthorized cross-owner entries +} diff --git a/server/src/schema/tables/asset-exif.table.ts b/server/src/schema/tables/asset-exif.table.ts index ae47ecfb10..d725189e6d 100644 --- a/server/src/schema/tables/asset-exif.table.ts +++ b/server/src/schema/tables/asset-exif.table.ts @@ -111,7 +111,7 @@ export class AssetExifTable { tags!: string[] | null; @UpdateDateColumn({ default: () => 'clock_timestamp()' }) - updatedAt!: Generated; + updatedAt!: Generated; @UpdateIdColumn({ index: true }) updateId!: Generated; diff --git a/server/src/schema/tables/notification.table.ts b/server/src/schema/tables/notification.table.ts index 6bf65808f1..fd1afc35c7 100644 --- a/server/src/schema/tables/notification.table.ts +++ b/server/src/schema/tables/notification.table.ts @@ -41,7 +41,7 @@ export class NotificationTable { type!: Generated; @Column({ type: 'jsonb', nullable: true }) - data!: any | null; + data!: unknown | null; @Column() title!: string; diff --git a/server/src/schema/tables/plugin-method.table.ts b/server/src/schema/tables/plugin-method.table.ts new file mode 100644 index 0000000000..10cd7b449e --- /dev/null +++ b/server/src/schema/tables/plugin-method.table.ts @@ -0,0 +1,35 @@ +import { Column, ForeignKeyColumn, Generated, PrimaryGeneratedColumn, Table, Unique } from '@immich/sql-tools'; +import { JsonSchemaDto } from 'src/dtos/json-schema.dto'; +import { WorkflowType } from 'src/enum'; +import { PluginTable } from 'src/schema/tables/plugin.table'; + +@Unique({ columns: ['pluginId', 'name'] }) +@Table('plugin_method') +export class PluginMethodTable { + @PrimaryGeneratedColumn('uuid') + id!: Generated; + + @ForeignKeyColumn(() => PluginTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) + pluginId!: string; + + @Column() + name!: string; + + @Column() + title!: string; + + @Column() + description!: string; + + @Column({ type: 'character varying', array: true }) + types!: Generated; + + @Column({ type: 'boolean', default: false }) + hostFunctions!: Generated; + + @Column({ type: 'jsonb', nullable: true }) + schema!: JsonSchemaDto | null; + + @Column({ type: 'character varying', default: [], array: true }) + uiHints!: Generated; +} diff --git a/server/src/schema/tables/plugin.table.ts b/server/src/schema/tables/plugin.table.ts index 5f82807f23..5de5a329d1 100644 --- a/server/src/schema/tables/plugin.table.ts +++ b/server/src/schema/tables/plugin.table.ts @@ -1,25 +1,30 @@ import { Column, CreateDateColumn, - ForeignKeyColumn, Generated, - Index, PrimaryGeneratedColumn, Table, Timestamp, + Unique, UpdateDateColumn, } from '@immich/sql-tools'; -import { PluginContext } from 'src/enum'; -import type { JSONSchema } from 'src/types/plugin-schema.types'; +import { PluginTemplate } from 'src/dtos/plugin.dto'; +@Unique({ columns: ['name', 'version'] }) @Table('plugin') export class PluginTable { @PrimaryGeneratedColumn('uuid') id!: Generated; + @Column({ type: 'boolean', default: true }) + enabled!: Generated; + @Column({ index: true, unique: true }) name!: string; + @Column() + version!: string; + @Column() title!: string; @@ -29,11 +34,14 @@ export class PluginTable { @Column() author!: string; - @Column() - version!: string; + @Column({ type: 'bytea' }) + wasmBytes!: Buffer; - @Column() - wasmPath!: string; + @Column({ type: 'jsonb' }) + templates!: PluginTemplate[]; + + @Column({ type: 'bytea' }) + sha256hash!: Buffer; @CreateDateColumn() createdAt!: Generated; @@ -41,55 +49,3 @@ export class PluginTable { @UpdateDateColumn() updatedAt!: Generated; } - -@Index({ columns: ['supportedContexts'], using: 'gin' }) -@Table('plugin_filter') -export class PluginFilterTable { - @PrimaryGeneratedColumn('uuid') - id!: Generated; - - @ForeignKeyColumn(() => PluginTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) - @Column({ index: true }) - pluginId!: string; - - @Column({ index: true, unique: true }) - methodName!: string; - - @Column() - title!: string; - - @Column() - description!: string; - - @Column({ type: 'character varying', array: true }) - supportedContexts!: Generated; - - @Column({ type: 'jsonb', nullable: true }) - schema!: JSONSchema | null; -} - -@Index({ columns: ['supportedContexts'], using: 'gin' }) -@Table('plugin_action') -export class PluginActionTable { - @PrimaryGeneratedColumn('uuid') - id!: Generated; - - @ForeignKeyColumn(() => PluginTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) - @Column({ index: true }) - pluginId!: string; - - @Column({ index: true, unique: true }) - methodName!: string; - - @Column() - title!: string; - - @Column() - description!: string; - - @Column({ type: 'character varying', array: true }) - supportedContexts!: Generated; - - @Column({ type: 'jsonb', nullable: true }) - schema!: JSONSchema | null; -} diff --git a/server/src/schema/tables/workflow-step.table.ts b/server/src/schema/tables/workflow-step.table.ts new file mode 100644 index 0000000000..49cb58c731 --- /dev/null +++ b/server/src/schema/tables/workflow-step.table.ts @@ -0,0 +1,26 @@ +import { WorkflowStepConfig } from '@immich/plugin-sdk'; +import { Column, ForeignKeyColumn, PrimaryGeneratedColumn, Table } from '@immich/sql-tools'; +import { Generated } from 'kysely'; +import { PluginMethodTable } from 'src/schema/tables/plugin-method.table'; +import { WorkflowTable } from 'src/schema/tables/workflow.table'; + +@Table('workflow_step') +export class WorkflowStepTable { + @PrimaryGeneratedColumn('uuid') + id!: Generated; + + @Column({ type: 'boolean', default: true }) + enabled!: boolean; + + @ForeignKeyColumn(() => WorkflowTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) + workflowId!: string; + + @ForeignKeyColumn(() => PluginMethodTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) + pluginMethodId!: string; + + @Column({ type: 'jsonb', nullable: true }) + config!: WorkflowStepConfig | null; + + @Column({ type: 'integer' }) + order!: number; +} diff --git a/server/src/schema/tables/workflow.table.ts b/server/src/schema/tables/workflow.table.ts index 163518e039..944fffd9d5 100644 --- a/server/src/schema/tables/workflow.table.ts +++ b/server/src/schema/tables/workflow.table.ts @@ -1,19 +1,19 @@ +import { WorkflowTrigger } from '@immich/plugin-sdk'; import { Column, CreateDateColumn, ForeignKeyColumn, Generated, - Index, PrimaryGeneratedColumn, Table, Timestamp, + UpdateDateColumn, } from '@immich/sql-tools'; -import { PluginTriggerType } from 'src/enum'; -import { PluginActionTable, PluginFilterTable } from 'src/schema/tables/plugin.table'; +import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; import { UserTable } from 'src/schema/tables/user.table'; -import type { ActionConfig, FilterConfig } from 'src/types/plugin-schema.types'; @Table('workflow') +@UpdatedAtTrigger('workflow_updatedAt') export class WorkflowTable { @PrimaryGeneratedColumn() id!: Generated; @@ -22,57 +22,23 @@ export class WorkflowTable { ownerId!: string; @Column() - triggerType!: PluginTriggerType; + trigger!: WorkflowTrigger; @Column({ nullable: true }) name!: string | null; - @Column() - description!: string; + @Column({ nullable: true }) + description!: string | null; @CreateDateColumn() createdAt!: Generated; + @UpdateDateColumn() + updatedAt!: Generated; + + @UpdateIdColumn() + updateId!: Generated; + @Column({ type: 'boolean', default: true }) - enabled!: boolean; -} - -@Index({ columns: ['workflowId', 'order'] }) -@Index({ columns: ['pluginFilterId'] }) -@Table('workflow_filter') -export class WorkflowFilterTable { - @PrimaryGeneratedColumn('uuid') - id!: Generated; - - @ForeignKeyColumn(() => WorkflowTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) - workflowId!: Generated; - - @ForeignKeyColumn(() => PluginFilterTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) - pluginFilterId!: string; - - @Column({ type: 'jsonb', nullable: true }) - filterConfig!: FilterConfig | null; - - @Column({ type: 'integer' }) - order!: number; -} - -@Index({ columns: ['workflowId', 'order'] }) -@Index({ columns: ['pluginActionId'] }) -@Table('workflow_action') -export class WorkflowActionTable { - @PrimaryGeneratedColumn('uuid') - id!: Generated; - - @ForeignKeyColumn(() => WorkflowTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) - workflowId!: Generated; - - @ForeignKeyColumn(() => PluginActionTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) - pluginActionId!: string; - - @Column({ type: 'jsonb', nullable: true }) - actionConfig!: ActionConfig | null; - - @Column({ type: 'integer' }) - order!: number; + enabled!: Generated; } diff --git a/server/src/services/album.service.spec.ts b/server/src/services/album.service.spec.ts index 24e28a9701..288c3c1d3c 100644 --- a/server/src/services/album.service.spec.ts +++ b/server/src/services/album.service.spec.ts @@ -26,18 +26,16 @@ describe(AlbumService.name, () => { describe('getStatistics', () => { it('should get the album count', async () => { - mocks.album.getOwned.mockResolvedValue([]); - mocks.album.getShared.mockResolvedValue([]); - mocks.album.getNotShared.mockResolvedValue([]); + mocks.album.getAll.mockResolvedValue([]); await expect(sut.getStatistics(authStub.admin)).resolves.toEqual({ owned: 0, shared: 0, notShared: 0, }); - expect(mocks.album.getOwned).toHaveBeenCalledWith(authStub.admin.user.id); - expect(mocks.album.getShared).toHaveBeenCalledWith(authStub.admin.user.id); - expect(mocks.album.getNotShared).toHaveBeenCalledWith(authStub.admin.user.id); + expect(mocks.album.getAll).toHaveBeenCalledWith(authStub.admin.user.id, { isOwned: true }); + expect(mocks.album.getAll).toHaveBeenCalledWith(authStub.admin.user.id, { isShared: true }); + expect(mocks.album.getAll).toHaveBeenCalledWith(authStub.admin.user.id, { isOwned: true, isShared: false }); }); }); @@ -46,7 +44,7 @@ describe(AlbumService.name, () => { const album = AlbumFactory.from().albumUser().build(); const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!; const sharedWithUserAlbum = AlbumFactory.from().owner(owner).albumUser().build(); - mocks.album.getOwned.mockResolvedValue([getForAlbum(album), getForAlbum(sharedWithUserAlbum)]); + mocks.album.getAll.mockResolvedValue([getForAlbum(album), getForAlbum(sharedWithUserAlbum)]); mocks.album.getMetadataForIds.mockResolvedValue([ { albumId: album.id, @@ -68,6 +66,7 @@ describe(AlbumService.name, () => { expect(result).toHaveLength(2); expect(result[0].id).toEqual(album.id); expect(result[1].id).toEqual(sharedWithUserAlbum.id); + expect(mocks.album.getAll).toHaveBeenCalledWith(owner.id, { isOwned: undefined, isShared: undefined }); }); it('gets list of albums that have a specific asset', async () => { @@ -98,7 +97,7 @@ describe(AlbumService.name, () => { it('gets list of albums that are shared', async () => { const album = AlbumFactory.from().albumUser().build(); const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!; - mocks.album.getShared.mockResolvedValue([getForAlbum(album)]); + mocks.album.getAll.mockResolvedValue([getForAlbum(album)]); mocks.album.getMetadataForIds.mockResolvedValue([ { albumId: album.id, @@ -109,16 +108,16 @@ describe(AlbumService.name, () => { }, ]); - const result = await sut.getAll(AuthFactory.create(owner), { shared: true }); + const result = await sut.getAll(AuthFactory.create(owner), { isShared: true }); expect(result).toHaveLength(1); expect(result[0].id).toEqual(album.id); - expect(mocks.album.getShared).toHaveBeenCalledTimes(1); + expect(mocks.album.getAll).toHaveBeenCalledWith(owner.id, expect.objectContaining({ isShared: true })); }); it('gets list of albums that are NOT shared', async () => { const album = AlbumFactory.create(); const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!; - mocks.album.getNotShared.mockResolvedValue([getForAlbum(album)]); + mocks.album.getAll.mockResolvedValue([getForAlbum(album)]); mocks.album.getMetadataForIds.mockResolvedValue([ { albumId: album.id, @@ -129,17 +128,66 @@ describe(AlbumService.name, () => { }, ]); - const result = await sut.getAll(AuthFactory.create(owner), { shared: false }); + const result = await sut.getAll(AuthFactory.create(owner), { isShared: false }); expect(result).toHaveLength(1); expect(result[0].id).toEqual(album.id); - expect(mocks.album.getNotShared).toHaveBeenCalledTimes(1); + expect(mocks.album.getAll).toHaveBeenCalledWith(owner.id, expect.objectContaining({ isShared: false })); + }); + + it('gets only owned albums when isOwned=true', async () => { + const album = AlbumFactory.create(); + const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!; + mocks.album.getAll.mockResolvedValue([getForAlbum(album)]); + mocks.album.getMetadataForIds.mockResolvedValue([ + { albumId: album.id, assetCount: 0, startDate: null, endDate: null, lastModifiedAssetTimestamp: null }, + ]); + + const result = await sut.getAll(AuthFactory.create(owner), { isOwned: true }); + expect(result).toHaveLength(1); + expect(mocks.album.getAll).toHaveBeenCalledWith(owner.id, expect.objectContaining({ isOwned: true })); + }); + + it('gets only shared-with-me albums when isOwned=false', async () => { + const album = AlbumFactory.create(); + const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!; + mocks.album.getAll.mockResolvedValue([getForAlbum(album)]); + mocks.album.getMetadataForIds.mockResolvedValue([ + { albumId: album.id, assetCount: 0, startDate: null, endDate: null, lastModifiedAssetTimestamp: null }, + ]); + + const result = await sut.getAll(AuthFactory.create(owner), { isOwned: false }); + expect(result).toHaveLength(1); + expect(mocks.album.getAll).toHaveBeenCalledWith(owner.id, expect.objectContaining({ isOwned: false })); + }); + + it('gets owned shared-out albums when isOwned=true and isShared=true', async () => { + const album = AlbumFactory.create(); + const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!; + mocks.album.getAll.mockResolvedValue([getForAlbum(album)]); + mocks.album.getMetadataForIds.mockResolvedValue([ + { albumId: album.id, assetCount: 0, startDate: null, endDate: null, lastModifiedAssetTimestamp: null }, + ]); + + const result = await sut.getAll(AuthFactory.create(owner), { isOwned: true, isShared: true }); + expect(result).toHaveLength(1); + expect(mocks.album.getAll).toHaveBeenCalledWith(owner.id, { isOwned: true, isShared: true }); + }); + + it('returns empty list when isOwned=false and isShared=false', async () => { + const album = AlbumFactory.create(); + const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!; + mocks.album.getAll.mockResolvedValue([]); + + const result = await sut.getAll(AuthFactory.create(owner), { isOwned: false, isShared: false }); + expect(result).toHaveLength(0); + expect(mocks.album.getAll).toHaveBeenCalledWith(owner.id, { isOwned: false, isShared: false }); }); }); it('counts assets correctly', async () => { const album = AlbumFactory.create(); const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!; - mocks.album.getOwned.mockResolvedValue([getForAlbum(album)]); + mocks.album.getAll.mockResolvedValue([getForAlbum(album)]); mocks.album.getMetadataForIds.mockResolvedValue([ { albumId: album.id, @@ -153,7 +201,7 @@ describe(AlbumService.name, () => { const result = await sut.getAll(AuthFactory.create(owner), {}); expect(result).toHaveLength(1); expect(result[0].assetCount).toEqual(1); - expect(mocks.album.getOwned).toHaveBeenCalledTimes(1); + expect(mocks.album.getAll).toHaveBeenCalledTimes(1); }); describe('create', () => { diff --git a/server/src/services/album.service.ts b/server/src/services/album.service.ts index 7bfc0bdcc2..564f4bfb3f 100644 --- a/server/src/services/album.service.ts +++ b/server/src/services/album.service.ts @@ -8,7 +8,6 @@ import { CreateAlbumDto, GetAlbumsDto, mapAlbum, - MapAlbumDto, UpdateAlbumDto, UpdateAlbumUserDto, } from 'src/dtos/album.dto'; @@ -19,16 +18,16 @@ import { AlbumUserRole, Permission } from 'src/enum'; import { AlbumAssetCount, AlbumInfoOptions } from 'src/repositories/album.repository'; import { BaseService } from 'src/services/base.service'; import { addAssets, removeAssets } from 'src/utils/asset.util'; -import { asDateString } from 'src/utils/date'; +import { asDateTimeString } from 'src/utils/date'; import { getPreferences } from 'src/utils/preferences'; @Injectable() export class AlbumService extends BaseService { async getStatistics(auth: AuthDto): Promise { const [owned, shared, notShared] = await Promise.all([ - this.albumRepository.getOwned(auth.user.id), - this.albumRepository.getShared(auth.user.id), - this.albumRepository.getNotShared(auth.user.id), + this.albumRepository.getAll(auth.user.id, { isOwned: true }), + this.albumRepository.getAll(auth.user.id, { isShared: true }), + this.albumRepository.getAll(auth.user.id, { isOwned: true, isShared: false }), ]); return { @@ -38,18 +37,15 @@ export class AlbumService extends BaseService { }; } - async getAll({ user: { id: ownerId } }: AuthDto, { assetId, shared }: GetAlbumsDto): Promise { + async getAll({ user: { id: ownerId } }: AuthDto, { assetId, ...rest }: GetAlbumsDto): Promise { await this.albumRepository.updateThumbnails(); - let albums: MapAlbumDto[]; - if (assetId) { - albums = await this.albumRepository.getByAssetId(ownerId, assetId); - } else if (shared === true) { - albums = await this.albumRepository.getShared(ownerId); - } else if (shared === false) { - albums = await this.albumRepository.getNotShared(ownerId); - } else { - albums = await this.albumRepository.getOwned(ownerId); + const albums = assetId + ? await this.albumRepository.getByAssetId(ownerId, assetId) + : await this.albumRepository.getAll(ownerId, rest); + + if (albums.length === 0) { + return []; } // Get asset count for each album. Then map the result to an object: @@ -63,11 +59,11 @@ export class AlbumService extends BaseService { return albums.map((album) => ({ ...mapAlbum(album), sharedLinks: undefined, - startDate: asDateString(albumMetadata[album.id]?.startDate ?? undefined), - endDate: asDateString(albumMetadata[album.id]?.endDate ?? undefined), + startDate: asDateTimeString(albumMetadata[album.id]?.startDate ?? undefined), + endDate: asDateTimeString(albumMetadata[album.id]?.endDate ?? undefined), assetCount: albumMetadata[album.id]?.assetCount ?? 0, // lastModifiedAssetTimestamp is only used in mobile app, please remove if not need - lastModifiedAssetTimestamp: asDateString(albumMetadata[album.id]?.lastModifiedAssetTimestamp ?? undefined), + lastModifiedAssetTimestamp: asDateTimeString(albumMetadata[album.id]?.lastModifiedAssetTimestamp ?? undefined), })); } @@ -83,10 +79,10 @@ export class AlbumService extends BaseService { return { ...mapAlbum(album), - startDate: asDateString(albumMetadataForIds?.startDate ?? undefined), - endDate: asDateString(albumMetadataForIds?.endDate ?? undefined), + startDate: asDateTimeString(albumMetadataForIds?.startDate ?? undefined), + endDate: asDateTimeString(albumMetadataForIds?.endDate ?? undefined), assetCount: albumMetadataForIds?.assetCount ?? 0, - lastModifiedAssetTimestamp: asDateString(albumMetadataForIds?.lastModifiedAssetTimestamp ?? undefined), + lastModifiedAssetTimestamp: asDateTimeString(albumMetadataForIds?.lastModifiedAssetTimestamp ?? undefined), contributorCounts: isShared ? await this.albumRepository.getContributorCounts(album.id) : undefined, }; } diff --git a/server/src/services/asset-media.service.spec.ts b/server/src/services/asset-media.service.spec.ts index cb3cc4d62a..9ef72a0897 100644 --- a/server/src/services/asset-media.service.spec.ts +++ b/server/src/services/asset-media.service.spec.ts @@ -339,7 +339,6 @@ describe(AssetMediaService.name, () => { }); expect(mocks.asset.create).toHaveBeenCalled(); - expect(mocks.user.updateUsage).toHaveBeenCalledWith(authStub.user1.user.id, file.size); expect(mocks.storage.utimes).toHaveBeenCalledWith( file.originalPath, expect.any(Date), diff --git a/server/src/services/asset-media.service.ts b/server/src/services/asset-media.service.ts index 6b0d73b77b..de8c2bbc91 100644 --- a/server/src/services/asset-media.service.ts +++ b/server/src/services/asset-media.service.ts @@ -146,17 +146,79 @@ export class AssetMediaService extends BaseService { { userId: auth.user.id, livePhotoVideoId: dto.livePhotoVideoId }, ); } - const asset = await this.create(auth.user.id, dto, file, sidecarFile); + + const asset = await this.assetRepository.create({ + ownerId: auth.user.id, + libraryId: null, + + checksum: file.checksum, + checksumAlgorithm: ChecksumAlgorithm.sha1File, + originalPath: file.originalPath, + + fileCreatedAt: dto.fileCreatedAt, + fileModifiedAt: dto.fileModifiedAt, + localDateTime: dto.fileCreatedAt, + + type: mimeTypes.assetType(file.originalPath), + isFavorite: dto.isFavorite, + duration: dto.duration || null, + visibility: dto.visibility ?? AssetVisibility.Timeline, + livePhotoVideoId: dto.livePhotoVideoId, + originalFileName: dto.filename || file.originalName, + }); + + if (dto.metadata?.length) { + await this.assetRepository.upsertMetadata(asset.id, dto.metadata); + } + + if (sidecarFile) { + await this.assetRepository.upsertFile({ + assetId: asset.id, + path: sidecarFile.originalPath, + type: AssetFileType.Sidecar, + }); + await this.storageRepository.utimes(sidecarFile.originalPath, new Date(), new Date(dto.fileModifiedAt)); + } + await this.storageRepository.utimes(file.originalPath, new Date(), new Date(dto.fileModifiedAt)); + await this.assetRepository.upsertExif({ + exif: { assetId: asset.id, fileSizeInByte: file.size }, + lockedPropertiesBehavior: 'override', + }); + + await this.jobRepository.queue({ name: JobName.AssetExtractMetadata, data: { id: asset.id, source: 'upload' } }); if (auth.sharedLink) { await this.addToSharedLink(auth.sharedLink, asset.id); } - await this.userRepository.updateUsage(auth.user.id, file.size); + await this.eventRepository.emit('AssetCreate', { asset, file }); return { id: asset.id, status: AssetMediaStatus.CREATED }; } catch (error: any) { - return this.handleUploadError(error, auth, file, sidecarFile); + // clean up files + await this.jobRepository.queue({ + name: JobName.FileDelete, + data: { files: [file.originalPath, sidecarFile?.originalPath] }, + }); + + // handle duplicates with a success response + if (isAssetChecksumConstraint(error)) { + const duplicateId = await this.assetRepository.getUploadAssetIdByChecksum(auth.user.id, file.checksum); + if (!duplicateId) { + this.logger.error(`Error locating duplicate for checksum constraint`); + throw new InternalServerErrorException(); + } + + if (auth.sharedLink) { + await this.addToSharedLink(auth.sharedLink, duplicateId); + } + + this.logger.debug(`Duplicate asset upload rejected: existing asset ${duplicateId}`); + return { status: AssetMediaStatus.DUPLICATE, id: duplicateId }; + } + + this.logger.error(`Error uploading file ${error}`, error?.stack); + throw error; } } @@ -285,84 +347,6 @@ export class AssetMediaService extends BaseService { : this.sharedLinkRepository.addAssets(sharedLink.id, [assetId])); } - private async handleUploadError( - error: any, - auth: AuthDto, - file: UploadFile, - sidecarFile?: UploadFile, - ): Promise { - // clean up files - await this.jobRepository.queue({ - name: JobName.FileDelete, - data: { files: [file.originalPath, sidecarFile?.originalPath] }, - }); - - // handle duplicates with a success response - if (isAssetChecksumConstraint(error)) { - const duplicateId = await this.assetRepository.getUploadAssetIdByChecksum(auth.user.id, file.checksum); - if (!duplicateId) { - this.logger.error(`Error locating duplicate for checksum constraint`); - throw new InternalServerErrorException(); - } - - if (auth.sharedLink) { - await this.addToSharedLink(auth.sharedLink, duplicateId); - } - - this.logger.debug(`Duplicate asset upload rejected: existing asset ${duplicateId}`); - return { status: AssetMediaStatus.DUPLICATE, id: duplicateId }; - } - - this.logger.error(`Error uploading file ${error}`, error?.stack); - throw error; - } - - private async create(ownerId: string, dto: AssetMediaCreateDto, file: UploadFile, sidecarFile?: UploadFile) { - const asset = await this.assetRepository.create({ - ownerId, - libraryId: null, - - checksum: file.checksum, - checksumAlgorithm: ChecksumAlgorithm.sha1File, - originalPath: file.originalPath, - - fileCreatedAt: dto.fileCreatedAt, - fileModifiedAt: dto.fileModifiedAt, - localDateTime: dto.fileCreatedAt, - - type: mimeTypes.assetType(file.originalPath), - isFavorite: dto.isFavorite, - duration: dto.duration || null, - visibility: dto.visibility ?? AssetVisibility.Timeline, - livePhotoVideoId: dto.livePhotoVideoId, - originalFileName: dto.filename || file.originalName, - }); - - if (dto.metadata?.length) { - await this.assetRepository.upsertMetadata(asset.id, dto.metadata); - } - - if (sidecarFile) { - await this.assetRepository.upsertFile({ - assetId: asset.id, - path: sidecarFile.originalPath, - type: AssetFileType.Sidecar, - }); - await this.storageRepository.utimes(sidecarFile.originalPath, new Date(), new Date(dto.fileModifiedAt)); - } - await this.storageRepository.utimes(file.originalPath, new Date(), new Date(dto.fileModifiedAt)); - await this.assetRepository.upsertExif({ - exif: { assetId: asset.id, fileSizeInByte: file.size }, - lockedPropertiesBehavior: 'override', - }); - - await this.eventRepository.emit('AssetCreate', { asset }); - - await this.jobRepository.queue({ name: JobName.AssetExtractMetadata, data: { id: asset.id, source: 'upload' } }); - - return asset; - } - private requireQuota(auth: AuthDto, size: number) { if (auth.user.quotaSizeInBytes !== null && auth.user.quotaSizeInBytes < auth.user.quotaUsageInBytes + size) { throw new BadRequestException('Quota has been exceeded!'); diff --git a/server/src/services/base.service.ts b/server/src/services/base.service.ts index d930dd0a31..33534f16de 100644 --- a/server/src/services/base.service.ts +++ b/server/src/services/base.service.ts @@ -58,6 +58,7 @@ import { ViewRepository } from 'src/repositories/view-repository'; import { WebsocketRepository } from 'src/repositories/websocket.repository'; import { WorkflowRepository } from 'src/repositories/workflow.repository'; import { UserTable } from 'src/schema/tables/user.table'; +import { ClassConstructor } from 'src/types'; import { AccessRequest, checkAccess, requireAccess } from 'src/utils/access'; import { getConfig, updateConfig } from 'src/utils/config'; @@ -187,6 +188,66 @@ export class BaseService { ); } + static create(Service: ClassConstructor, ctx: BaseService) { + const service = new Service( + LoggingRepository.create(), + ctx.accessRepository, + ctx.activityRepository, + ctx.albumRepository, + ctx.albumUserRepository, + ctx.apiKeyRepository, + ctx.appRepository, + ctx.assetRepository, + ctx.assetEditRepository, + ctx.assetJobRepository, + ctx.configRepository, + ctx.cronRepository, + ctx.cryptoRepository, + ctx.databaseRepository, + ctx.downloadRepository, + ctx.duplicateRepository, + ctx.emailRepository, + ctx.eventRepository, + ctx.jobRepository, + ctx.libraryRepository, + ctx.machineLearningRepository, + ctx.mapRepository, + ctx.mediaRepository, + ctx.memoryRepository, + ctx.metadataRepository, + ctx.moveRepository, + ctx.notificationRepository, + ctx.oauthRepository, + ctx.ocrRepository, + ctx.partnerRepository, + ctx.personRepository, + ctx.pluginRepository, + ctx.processRepository, + ctx.searchRepository, + ctx.serverInfoRepository, + ctx.sessionRepository, + ctx.sharedLinkRepository, + ctx.sharedLinkAssetRepository, + ctx.stackRepository, + ctx.storageRepository, + ctx.syncRepository, + ctx.syncCheckpointRepository, + ctx.systemMetadataRepository, + ctx.tagRepository, + ctx.telemetryRepository, + ctx.trashRepository, + ctx.userRepository, + ctx.versionRepository, + ctx.viewRepository, + ctx.websocketRepository, + ctx.workflowRepository, + ); + + service.logger.setContext(this.name); + + return service as T; + } + get worker() { return this.configRepository.getWorker(); } diff --git a/server/src/services/duplicate.service.spec.ts b/server/src/services/duplicate.service.spec.ts index 564cffa0bc..18e3e664b5 100644 --- a/server/src/services/duplicate.service.spec.ts +++ b/server/src/services/duplicate.service.spec.ts @@ -1,3 +1,4 @@ +import { BadRequestException } from '@nestjs/common'; import { BulkIdErrorReason } from 'src/dtos/asset-ids.response.dto'; import { MapAsset } from 'src/dtos/asset-response.dto'; import { AssetType, AssetVisibility, JobName, JobStatus } from 'src/enum'; @@ -149,6 +150,36 @@ describe(DuplicateService.name, () => { }); }); + describe('delete', () => { + it('should throw for an unknown or unauthorized group id', async () => { + mocks.access.duplicate.checkOwnerAccess.mockResolvedValue(new Set()); + await expect(sut.delete(authStub.admin, 'group-1')).rejects.toThrow(BadRequestException); + expect(mocks.duplicateRepository.delete).not.toHaveBeenCalled(); + }); + + it('should dismiss the duplicate group', async () => { + mocks.access.duplicate.checkOwnerAccess.mockResolvedValue(new Set(['group-1'])); + mocks.duplicateRepository.delete.mockResolvedValue(); + await expect(sut.delete(authStub.admin, 'group-1')).resolves.toBeUndefined(); + expect(mocks.duplicateRepository.delete).toHaveBeenCalledWith(authStub.admin.user.id, 'group-1'); + }); + }); + + describe('deleteAll', () => { + it('should throw if any group id is unknown or unauthorized', async () => { + mocks.access.duplicate.checkOwnerAccess.mockResolvedValue(new Set(['group-1'])); + await expect(sut.deleteAll(authStub.admin, { ids: ['group-1', 'group-2'] })).rejects.toThrow(BadRequestException); + expect(mocks.duplicateRepository.deleteAll).not.toHaveBeenCalled(); + }); + + it('should dismiss all duplicate groups', async () => { + mocks.access.duplicate.checkOwnerAccess.mockResolvedValue(new Set(['group-1', 'group-2'])); + mocks.duplicateRepository.deleteAll.mockResolvedValue(); + await expect(sut.deleteAll(authStub.admin, { ids: ['group-1', 'group-2'] })).resolves.toBeUndefined(); + expect(mocks.duplicateRepository.deleteAll).toHaveBeenCalledWith(authStub.admin.user.id, ['group-1', 'group-2']); + }); + }); + describe('resolve', () => { it('should handle mixed success and failure', async () => { const asset = AssetFactory.create(); diff --git a/server/src/services/duplicate.service.ts b/server/src/services/duplicate.service.ts index 39123e031c..6e9e62ba0b 100644 --- a/server/src/services/duplicate.service.ts +++ b/server/src/services/duplicate.service.ts @@ -82,10 +82,12 @@ export class DuplicateService extends BaseService { } async delete(auth: AuthDto, id: string): Promise { + await this.requireAccess({ auth, permission: Permission.DuplicateDelete, ids: [id] }); await this.duplicateRepository.delete(auth.user.id, id); } async deleteAll(auth: AuthDto, dto: BulkIdsDto) { + await this.requireAccess({ auth, permission: Permission.DuplicateDelete, ids: dto.ids }); await this.duplicateRepository.deleteAll(auth.user.id, dto.ids); } diff --git a/server/src/services/hls.service.spec.ts b/server/src/services/hls.service.spec.ts new file mode 100644 index 0000000000..ccbba48107 --- /dev/null +++ b/server/src/services/hls.service.spec.ts @@ -0,0 +1,327 @@ +import { BadRequestException, NotFoundException } from '@nestjs/common'; +import { TranscodeHardwareAcceleration } from 'src/enum'; +import { HlsService } from 'src/services/hls.service'; +import { eiffelTower, train, waterfall } from 'test/fixtures/media.stub'; +import { factory } from 'test/small.factory'; +import { newTestService, ServiceMocks } from 'test/utils'; + +// EXTINF values come from FFmpeg's playlist to enforce an exact match +const eiffelExpectedMediaPlaylist = `#EXTM3U +#EXT-X-VERSION:7 +#EXT-X-TARGETDURATION:2 +#EXT-X-MEDIA-SEQUENCE:0 +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-MAP:URI="init.mp4" +#EXTINF:2.007222, +seg_0.m4s +#EXTINF:2.007222, +seg_1.m4s +#EXTINF:2.007222, +seg_2.m4s +#EXTINF:2.007222, +seg_3.m4s +#EXTINF:2.007222, +seg_4.m4s +#EXTINF:2.007222, +seg_5.m4s +#EXTINF:2.007222, +seg_6.m4s +#EXTINF:2.007222, +seg_7.m4s +#EXTINF:2.007222, +seg_8.m4s +#EXTINF:2.007222, +seg_9.m4s +#EXTINF:2.007222, +seg_10.m4s +#EXTINF:0.281011, +seg_11.m4s +#EXT-X-ENDLIST +`; + +const waterfallExpectedMediaPlaylist = `#EXTM3U +#EXT-X-VERSION:7 +#EXT-X-TARGETDURATION:2 +#EXT-X-MEDIA-SEQUENCE:0 +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-MAP:URI="init.mp4" +#EXTINF:2.011405, +seg_0.m4s +#EXTINF:2.011405, +seg_1.m4s +#EXTINF:2.011405, +seg_2.m4s +#EXTINF:2.011405, +seg_3.m4s +#EXTINF:2.011405, +seg_4.m4s +#EXTINF:0.301711, +seg_5.m4s +#EXT-X-ENDLIST +`; + +const trainExpectedMediaPlaylist = `#EXTM3U +#EXT-X-VERSION:7 +#EXT-X-TARGETDURATION:2 +#EXT-X-MEDIA-SEQUENCE:0 +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-MAP:URI="init.mp4" +#EXTINF:2.000000, +seg_0.m4s +#EXTINF:2.000000, +seg_1.m4s +#EXTINF:2.000000, +seg_2.m4s +#EXTINF:2.000000, +seg_3.m4s +#EXTINF:2.000000, +seg_4.m4s +#EXTINF:2.000000, +seg_5.m4s +#EXTINF:2.000000, +seg_6.m4s +#EXTINF:2.000000, +seg_7.m4s +#EXTINF:2.000000, +seg_8.m4s +#EXTINF:2.000000, +seg_9.m4s +#EXTINF:1.733333, +seg_10.m4s +#EXT-X-ENDLIST +`; + +const sessionId = '00000000-0000-0000-0000-000000000000'; + +const eiffelExpectedMasterDisabled = `#EXTM3U +#EXT-X-VERSION:7 +#EXT-X-STREAM-INF:BANDWIDTH=1000000,RESOLUTION=480x852,CODECS="av01.0.04M.08,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=24.910 +${sessionId}/0/playlist.m3u8 +#EXT-X-STREAM-INF:BANDWIDTH=1200000,RESOLUTION=480x852,CODECS="hvc1.1.6.L90.B0,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=24.910 +${sessionId}/1/playlist.m3u8 +#EXT-X-STREAM-INF:BANDWIDTH=2500000,RESOLUTION=480x852,CODECS="avc1.64001e,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=24.910 +${sessionId}/2/playlist.m3u8 +#EXT-X-STREAM-INF:BANDWIDTH=2000000,RESOLUTION=720x1280,CODECS="av01.0.08M.08,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=24.910 +${sessionId}/3/playlist.m3u8 +#EXT-X-STREAM-INF:BANDWIDTH=2500000,RESOLUTION=720x1280,CODECS="hvc1.1.6.L93.B0,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=24.910 +${sessionId}/4/playlist.m3u8 +#EXT-X-STREAM-INF:BANDWIDTH=5000000,RESOLUTION=720x1280,CODECS="avc1.64001f,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=24.910 +${sessionId}/5/playlist.m3u8 +#EXT-X-STREAM-INF:BANDWIDTH=4000000,RESOLUTION=1080x1920,CODECS="av01.0.09M.08,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=24.910 +${sessionId}/6/playlist.m3u8 +#EXT-X-STREAM-INF:BANDWIDTH=4500000,RESOLUTION=1080x1920,CODECS="hvc1.1.6.L120.B0,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=24.910 +${sessionId}/7/playlist.m3u8 +#EXT-X-STREAM-INF:BANDWIDTH=8000000,RESOLUTION=1080x1920,CODECS="avc1.640028,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=24.910 +${sessionId}/8/playlist.m3u8 +`; + +const eiffelExpectedMasterRkmpp = `#EXTM3U +#EXT-X-VERSION:7 +#EXT-X-STREAM-INF:BANDWIDTH=1200000,RESOLUTION=480x852,CODECS="hvc1.1.6.L90.B0,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=24.910 +${sessionId}/1/playlist.m3u8 +#EXT-X-STREAM-INF:BANDWIDTH=2500000,RESOLUTION=480x852,CODECS="avc1.64001e,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=24.910 +${sessionId}/2/playlist.m3u8 +#EXT-X-STREAM-INF:BANDWIDTH=2500000,RESOLUTION=720x1280,CODECS="hvc1.1.6.L93.B0,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=24.910 +${sessionId}/4/playlist.m3u8 +#EXT-X-STREAM-INF:BANDWIDTH=5000000,RESOLUTION=720x1280,CODECS="avc1.64001f,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=24.910 +${sessionId}/5/playlist.m3u8 +#EXT-X-STREAM-INF:BANDWIDTH=4500000,RESOLUTION=1080x1920,CODECS="hvc1.1.6.L120.B0,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=24.910 +${sessionId}/7/playlist.m3u8 +#EXT-X-STREAM-INF:BANDWIDTH=8000000,RESOLUTION=1080x1920,CODECS="avc1.640028,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=24.910 +${sessionId}/8/playlist.m3u8 +`; + +const waterfallExpectedMasterDisabled = `#EXTM3U +#EXT-X-VERSION:7 +#EXT-X-STREAM-INF:BANDWIDTH=1000000,RESOLUTION=480x852,CODECS="av01.0.04M.08,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=29.830 +${sessionId}/0/playlist.m3u8 +#EXT-X-STREAM-INF:BANDWIDTH=1200000,RESOLUTION=480x852,CODECS="hvc1.1.6.L90.B0,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=29.830 +${sessionId}/1/playlist.m3u8 +#EXT-X-STREAM-INF:BANDWIDTH=2500000,RESOLUTION=480x852,CODECS="avc1.64001e,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=29.830 +${sessionId}/2/playlist.m3u8 +#EXT-X-STREAM-INF:BANDWIDTH=2000000,RESOLUTION=720x1280,CODECS="av01.0.08M.08,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=29.830 +${sessionId}/3/playlist.m3u8 +#EXT-X-STREAM-INF:BANDWIDTH=2500000,RESOLUTION=720x1280,CODECS="hvc1.1.6.L93.B0,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=29.830 +${sessionId}/4/playlist.m3u8 +#EXT-X-STREAM-INF:BANDWIDTH=5000000,RESOLUTION=720x1280,CODECS="avc1.64001f,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=29.830 +${sessionId}/5/playlist.m3u8 +#EXT-X-STREAM-INF:BANDWIDTH=4000000,RESOLUTION=1080x1920,CODECS="av01.0.09M.08,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=29.830 +${sessionId}/6/playlist.m3u8 +#EXT-X-STREAM-INF:BANDWIDTH=4500000,RESOLUTION=1080x1920,CODECS="hvc1.1.6.L120.B0,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=29.830 +${sessionId}/7/playlist.m3u8 +#EXT-X-STREAM-INF:BANDWIDTH=8000000,RESOLUTION=1080x1920,CODECS="avc1.640028,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=29.830 +${sessionId}/8/playlist.m3u8 +`; + +describe(HlsService.name, () => { + let sut: HlsService; + let mocks: ServiceMocks; + + beforeEach(() => { + ({ sut, mocks } = newTestService(HlsService)); + }); + + describe('getMainPlaylist', () => { + const auth = factory.auth(); + const assetId = 'asset-1'; + + const setup = (asset: typeof eiffelTower | typeof waterfall, accel: TranscodeHardwareAcceleration) => { + mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([assetId])); + mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { realtime: { enabled: true }, accel } }); + mocks.videoStream.getForMainPlaylist.mockResolvedValue(asset); + mocks.crypto.randomUUID.mockReturnValue(sessionId); + mocks.websocket.serverSend.mockImplementation((event, ...rest) => { + if (event === 'HlsSessionRequest') { + const { sessionId: id } = rest[0] as { sessionId: string }; + queueMicrotask(() => sut.onSessionResult({ sessionId: id })); + } + }); + }; + + it('returns main playlist for eiffel-tower (1080p portrait, no acceleration)', async () => { + setup(eiffelTower, TranscodeHardwareAcceleration.Disabled); + await expect(sut.getMainPlaylist(auth, assetId)).resolves.toBe(eiffelExpectedMasterDisabled); + }); + + it('returns main playlist for eiffel-tower with RKMPP (no AV1 variants)', async () => { + setup(eiffelTower, TranscodeHardwareAcceleration.Rkmpp); + await expect(sut.getMainPlaylist(auth, assetId)).resolves.toBe(eiffelExpectedMasterRkmpp); + }); + + it('returns main playlist for waterfall (4K landscape) with no acceleration', async () => { + setup(waterfall, TranscodeHardwareAcceleration.Disabled); + await expect(sut.getMainPlaylist(auth, assetId)).resolves.toBe(waterfallExpectedMasterDisabled); + }); + + it('throws BadRequestException when realtime transcoding is disabled', async () => { + mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([assetId])); + mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { realtime: { enabled: false } } }); + await expect(sut.getMainPlaylist(auth, assetId)).rejects.toBeInstanceOf(BadRequestException); + }); + + it('throws NotFoundException when asset is not yet ready for streaming', async () => { + mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([assetId])); + mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { realtime: { enabled: true } } }); + await expect(sut.getMainPlaylist(auth, assetId)).rejects.toBeInstanceOf(NotFoundException); + }); + }); + + describe('getMediaPlaylist', () => { + const auth = factory.auth(); + const assetId = 'asset-1'; + const fixtures = [ + { data: eiffelTower, playlist: eiffelExpectedMediaPlaylist }, + { data: waterfall, playlist: waterfallExpectedMediaPlaylist }, + { data: train, playlist: trainExpectedMediaPlaylist }, + ]; + + it.each(fixtures)('matches FFmpeg for $data.originalPath', async ({ data, playlist }) => { + mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([assetId])); + mocks.videoStream.getForMediaPlaylist.mockResolvedValue(data); + await expect(sut.getMediaPlaylist(auth, assetId, sessionId)).resolves.toBe(playlist); + }); + + it('throws NotFoundException when the session/asset cannot be loaded', async () => { + mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([assetId])); + await expect(sut.getMediaPlaylist(auth, assetId, sessionId)).rejects.toBeInstanceOf(NotFoundException); + }); + }); + + describe('getSegment', () => { + const auth = factory.auth(); + const assetId = 'asset-1'; + const variantIndex = 0; + + beforeEach(() => { + mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([assetId])); + mocks.videoStream.getSession.mockResolvedValue({ id: sessionId, assetId } as never); + mocks.storage.checkFileExists.mockResolvedValue(true); + }); + + it('emits HlsHeartbeat with segmentIndex 0 for the first init.mp4 request', async () => { + await sut.getSegment(auth, assetId, sessionId, variantIndex, 'init.mp4'); + expect(mocks.websocket.serverSend).toHaveBeenCalledWith('HlsHeartbeat', { + sessionId, + variantIndex, + segmentIndex: 0, + }); + }); + + it('emits HlsHeartbeat with the parsed segment number for seg_K.m4s', async () => { + await sut.getSegment(auth, assetId, sessionId, variantIndex, 'seg_5.m4s'); + expect(mocks.websocket.serverSend).toHaveBeenCalledWith('HlsHeartbeat', { + sessionId, + variantIndex, + segmentIndex: 5, + }); + }); + + it('returns lastRequested + 1 for init.mp4 after a segment has been served', async () => { + await sut.getSegment(auth, assetId, sessionId, variantIndex, 'seg_5.m4s'); + mocks.websocket.serverSend.mockClear(); + await sut.getSegment(auth, assetId, sessionId, variantIndex, 'init.mp4'); + expect(mocks.websocket.serverSend).toHaveBeenCalledWith('HlsHeartbeat', { + sessionId, + variantIndex, + segmentIndex: 6, + }); + }); + + it('updates lastRequested on a backward-seek segment request', async () => { + await sut.getSegment(auth, assetId, sessionId, variantIndex, 'seg_5.m4s'); + await sut.getSegment(auth, assetId, sessionId, variantIndex, 'seg_3.m4s'); + mocks.websocket.serverSend.mockClear(); + await sut.getSegment(auth, assetId, sessionId, variantIndex, 'init.mp4'); + expect(mocks.websocket.serverSend).toHaveBeenCalledWith('HlsHeartbeat', { + sessionId, + variantIndex, + segmentIndex: 4, + }); + }); + + it('tracks segment state per session independently', async () => { + await sut.getSegment(auth, assetId, 'session-a', variantIndex, 'seg_5.m4s'); + await sut.getSegment(auth, assetId, 'session-b', variantIndex, 'seg_2.m4s'); + mocks.websocket.serverSend.mockClear(); + await sut.getSegment(auth, assetId, 'session-a', variantIndex, 'init.mp4'); + await sut.getSegment(auth, assetId, 'session-b', variantIndex, 'init.mp4'); + expect(mocks.websocket.serverSend).toHaveBeenCalledWith('HlsHeartbeat', { + sessionId: 'session-a', + variantIndex, + segmentIndex: 6, + }); + expect(mocks.websocket.serverSend).toHaveBeenCalledWith('HlsHeartbeat', { + sessionId: 'session-b', + variantIndex, + segmentIndex: 3, + }); + }); + + it('rejects pending waiters for the previous variant on variant change', async () => { + mocks.storage.checkFileExists.mockResolvedValueOnce(false); + + const pending = sut.getSegment(auth, assetId, sessionId, 0, 'seg_1.m4s'); + await new Promise((resolve) => setImmediate(resolve)); + await sut.getSegment(auth, assetId, sessionId, 1, 'seg_1.m4s'); + + await expect(pending).rejects.toThrow('Variant changed'); + }); + + it('throws NotFoundException when the session does not exist', async () => { + mocks.videoStream.getSession.mockReset(); + await expect(sut.getSegment(auth, assetId, sessionId, variantIndex, 'init.mp4')).rejects.toBeInstanceOf( + NotFoundException, + ); + }); + }); + + describe('endSession', () => { + it('emits HlsSessionEnd', async () => { + const auth = factory.auth(); + const assetId = 'asset-1'; + mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([assetId])); + await sut.endSession(auth, assetId, sessionId); + expect(mocks.websocket.serverSend).toHaveBeenCalledWith('HlsSessionEnd', { sessionId }); + }); + }); +}); diff --git a/server/src/services/hls.service.ts b/server/src/services/hls.service.ts new file mode 100644 index 0000000000..fba8b8e060 --- /dev/null +++ b/server/src/services/hls.service.ts @@ -0,0 +1,198 @@ +import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common'; +import { constants } from 'node:fs'; +import { join } from 'node:path'; +import { + HLS_SEGMENT_DURATION, + HLS_SEGMENT_FILENAME_REGEX, + HLS_VARIANTS, + HLS_VERSION, + SUPPORTED_HWA_CODECS, +} from 'src/constants'; +import { StorageCore } from 'src/cores/storage.core'; +import { OnEvent } from 'src/decorators'; +import { AuthDto } from 'src/dtos/auth.dto'; +import { SystemConfigFFmpegDto } from 'src/dtos/system-config.dto'; +import { CacheControl, ImmichWorker, Permission } from 'src/enum'; +import { ArgOf } from 'src/repositories/event.repository'; +import { BaseService } from 'src/services/base.service'; +import { VideoPacketInfo, VideoStreamInfo } from 'src/types'; +import { PendingEvents } from 'src/utils/event'; +import { ImmichFileResponse } from 'src/utils/file'; +import { getOutputSize } from 'src/utils/media'; + +type AssetWithStreamInfo = { videoStream: VideoStreamInfo & { timeBase: number }; packets: VideoPacketInfo }; +type ApiSession = { lastRequestedSegment: number | null; lastVariantIndex: number | null }; + +@Injectable() +export class HlsService extends BaseService { + private pendingSegments = new PendingEvents<'HlsSegmentResult'>({ timeoutMs: 15_000 }); + private pendingSessions = new PendingEvents<'HlsSessionResult'>({ timeoutMs: 5000 }); + private sessions = new Map(); + + @OnEvent({ name: 'HlsSessionResult', server: true, workers: [ImmichWorker.Api] }) + onSessionResult(event: ArgOf<'HlsSessionResult'>) { + this.pendingSessions.complete(event.sessionId, event); + if (event.error) { + this.sessions.delete(event.sessionId); + this.pendingSegments.rejectByPrefix(`${event.sessionId}:`, event.error); + } + } + + @OnEvent({ name: 'HlsSessionEnd', server: true, workers: [ImmichWorker.Api] }) + onSessionEnd({ sessionId }: ArgOf<'HlsSessionEnd'>) { + this.sessions.delete(sessionId); + this.pendingSegments.rejectByPrefix(`${sessionId}:`, 'Session ended'); + } + + @OnEvent({ name: 'HlsSegmentResult', server: true, workers: [ImmichWorker.Api] }) + onSegmentResult(event: ArgOf<'HlsSegmentResult'>) { + this.pendingSegments.complete(this.getSegmentKey(event), event); + } + + async getMainPlaylist(auth: AuthDto, assetId: string) { + await this.requireAccess({ auth, permission: Permission.AssetView, ids: [assetId] }); + const { ffmpeg } = await this.getConfig({ withCache: true }); + if (!ffmpeg.realtime.enabled) { + throw new BadRequestException('Real-time transcoding is not enabled'); + } + + const asset = await this.videoStreamRepository.getForMainPlaylist(assetId); + if (!asset) { + throw new NotFoundException('Asset is not yet ready for streaming'); + } + + // Sharing the sessionId allows only one microservices worker to successfully insert to the session table. + // The microservices worker that creates a session owns the transcoding lifecycle for it. + const sessionId = this.cryptoRepository.randomUUID(); + this.websocketRepository.serverSend('HlsSessionRequest', { sessionId, assetId, ownerId: auth.user.id }); + await this.pendingSessions.wait(sessionId); + this.trackSession(sessionId); + + return this.generateMainPlaylist(sessionId, ffmpeg, asset); + } + + async getMediaPlaylist(auth: AuthDto, assetId: string, sessionId: string) { + await this.requireAccess({ auth, permission: Permission.AssetView, ids: [assetId] }); + + const asset = await this.videoStreamRepository.getForMediaPlaylist(assetId, sessionId); + if (!asset) { + throw new NotFoundException('Asset not found or not yet ready for streaming'); + } + + return this.generateMediaPlaylist(asset); + } + + async getSegment(auth: AuthDto, assetId: string, sessionId: string, variantIndex: number, filename: string) { + await this.requireAccess({ auth, permission: Permission.AssetView, ids: [assetId] }); + + const session = await this.videoStreamRepository.getSession(sessionId); + if (!session) { + throw new NotFoundException('Session not found'); + } + + const variantDir = StorageCore.getHlsVariantFolder({ ownerId: auth.user.id, sessionId, variantIndex }); + const path = join(variantDir, filename); + const response = new ImmichFileResponse({ + path, + contentType: 'video/mp4', + cacheControl: CacheControl.PrivateWithCache, + }); + + const apiSession = this.trackSession(sessionId, variantIndex); + const segmentIndex = this.getSegmentIndex(apiSession, filename); + this.websocketRepository.serverSend('HlsHeartbeat', { sessionId, variantIndex, segmentIndex }); + + if (await this.storageRepository.checkFileExists(path, constants.R_OK)) { + return response; + } + + this.websocketRepository.serverSend('HlsSegmentRequest', { sessionId, assetId, variantIndex, segmentIndex }); + await this.pendingSegments.wait(this.getSegmentKey({ sessionId, variantIndex, segmentIndex })); + + return response; + } + + async endSession(auth: AuthDto, assetId: string, sessionId: string): Promise { + await this.requireAccess({ auth, permission: Permission.AssetView, ids: [assetId] }); + + this.websocketRepository.serverSend('HlsSessionEnd', { sessionId }); + } + + private generateMainPlaylist(sessionId: string, ffmpeg: SystemConfigFFmpegDto, asset: AssetWithStreamInfo) { + const fps = ((asset.packets.packetCount * asset.videoStream.timeBase) / asset.packets.totalDuration).toFixed(3); + const sourceResolution = Math.min(asset.videoStream.height, asset.videoStream.width); + const targetResolution = Math.max(sourceResolution, HLS_VARIANTS[0].resolution); + const lines = ['#EXTM3U', `#EXT-X-VERSION:${HLS_VERSION}`]; + for (let i = 0; i < HLS_VARIANTS.length; i++) { + const { resolution, bitrate, codec, codecString } = HLS_VARIANTS[i]; + if (resolution > targetResolution || !SUPPORTED_HWA_CODECS[ffmpeg.accel].includes(codec)) { + continue; + } + const { width, height } = getOutputSize(asset.videoStream, resolution); + lines.push( + `#EXT-X-STREAM-INF:BANDWIDTH=${bitrate},RESOLUTION=${width}x${height},CODECS="${codecString},mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=${fps}`, + `${sessionId}/${i}/playlist.m3u8`, + ); + } + lines.push(''); + + if (lines.length === 3) { + throw new NotFoundException('No supported variants for this video'); + } + + return lines.join('\n'); + } + + private generateMediaPlaylist({ videoStream, packets }: AssetWithStreamInfo) { + const fps = (packets.packetCount * videoStream.timeBase) / packets.totalDuration; + const framesPerSegment = Math.ceil(HLS_SEGMENT_DURATION * fps); + const fullSegmentDuration = framesPerSegment / fps; + const segmentCount = Math.ceil(packets.outputFrames / framesPerSegment); + const lastSegmentFrames = packets.outputFrames - framesPerSegment * (segmentCount - 1); + const lastSegmentDuration = lastSegmentFrames / fps; + + const lines = [ + '#EXTM3U', + `#EXT-X-VERSION:${HLS_VERSION}`, + `#EXT-X-TARGETDURATION:${HLS_SEGMENT_DURATION}`, + '#EXT-X-MEDIA-SEQUENCE:0', + '#EXT-X-PLAYLIST-TYPE:VOD', + '#EXT-X-MAP:URI="init.mp4"', + ]; + + for (let i = 0; i < segmentCount - 1; i++) { + lines.push(`#EXTINF:${fullSegmentDuration.toFixed(6)},`, `seg_${i}.m4s`); + } + lines.push(`#EXTINF:${lastSegmentDuration.toFixed(6)},`, `seg_${segmentCount - 1}.m4s`, '#EXT-X-ENDLIST', ''); + + return lines.join('\n'); + } + + private getSegmentKey({ sessionId, variantIndex, segmentIndex }: ArgOf<'HlsSegmentResult'>) { + return `${sessionId}:${variantIndex}:${segmentIndex}`; + } + + private getSegmentIndex(session: ApiSession, filename: string) { + if (filename.endsWith('.mp4')) { + return (session.lastRequestedSegment ?? -1) + 1; + } + const segmentIndex = Number.parseInt(HLS_SEGMENT_FILENAME_REGEX.exec(filename)![1]); + session.lastRequestedSegment = segmentIndex; + return segmentIndex; + } + + private trackSession(id: string, variantIndex: number | null = null) { + const session = this.sessions.get(id); + if (!session) { + const newSession = { lastRequestedSegment: null, lastVariantIndex: variantIndex }; + this.sessions.set(id, newSession); + return newSession; + } + + if (session.lastVariantIndex !== null && session.lastVariantIndex !== variantIndex) { + this.pendingSegments.rejectByPrefix(`${id}:${session.lastVariantIndex}:`, 'Variant changed'); + } + session.lastVariantIndex = variantIndex; + return session; + } +} diff --git a/server/src/services/index.ts b/server/src/services/index.ts index f4e82b13a4..3c23e723bc 100644 --- a/server/src/services/index.ts +++ b/server/src/services/index.ts @@ -11,6 +11,7 @@ import { DatabaseBackupService } from 'src/services/database-backup.service'; import { DatabaseService } from 'src/services/database.service'; import { DownloadService } from 'src/services/download.service'; import { DuplicateService } from 'src/services/duplicate.service'; +import { HlsService } from 'src/services/hls.service'; import { JobService } from 'src/services/job.service'; import { LibraryService } from 'src/services/library.service'; import { MaintenanceService } from 'src/services/maintenance.service'; @@ -39,11 +40,13 @@ import { SystemMetadataService } from 'src/services/system-metadata.service'; import { TagService } from 'src/services/tag.service'; import { TelemetryService } from 'src/services/telemetry.service'; import { TimelineService } from 'src/services/timeline.service'; +import { TranscodingService } from 'src/services/transcoding.service'; import { TrashService } from 'src/services/trash.service'; import { UserAdminService } from 'src/services/user-admin.service'; import { UserService } from 'src/services/user.service'; import { VersionService } from 'src/services/version.service'; import { ViewService } from 'src/services/view.service'; +import { WorkflowExecutionService } from 'src/services/workflow-execution.service'; import { WorkflowService } from 'src/services/workflow.service'; export const services = [ @@ -60,6 +63,7 @@ export const services = [ DatabaseService, DownloadService, DuplicateService, + HlsService, JobService, LibraryService, MaintenanceService, @@ -88,10 +92,12 @@ export const services = [ TagService, TelemetryService, TimelineService, + TranscodingService, TrashService, UserAdminService, UserService, VersionService, ViewService, + WorkflowExecutionService, WorkflowService, ]; diff --git a/server/src/services/job.service.ts b/server/src/services/job.service.ts index 8a714615c9..a8721a5fde 100644 --- a/server/src/services/job.service.ts +++ b/server/src/services/job.service.ts @@ -110,6 +110,7 @@ export class JobService extends BaseService { checksum: hexOrBufferToBase64(asset.checksum), fileCreatedAt: asset.fileCreatedAt, fileModifiedAt: asset.fileModifiedAt, + createdAt: asset.createdAt, localDateTime: asset.localDateTime, duration: asset.duration, type: asset.type, @@ -166,6 +167,7 @@ export class JobService extends BaseService { checksum: hexOrBufferToBase64(asset.checksum), fileCreatedAt: asset.fileCreatedAt, fileModifiedAt: asset.fileModifiedAt, + createdAt: asset.createdAt, localDateTime: asset.localDateTime, duration: asset.duration, type: asset.type, diff --git a/server/src/services/map.service.spec.ts b/server/src/services/map.service.spec.ts index fdf7aee68b..6ef94cd40c 100644 --- a/server/src/services/map.service.spec.ts +++ b/server/src/services/map.service.spec.ts @@ -4,7 +4,7 @@ import { AssetFactory } from 'test/factories/asset.factory'; import { AuthFactory } from 'test/factories/auth.factory'; import { PartnerFactory } from 'test/factories/partner.factory'; import { userStub } from 'test/fixtures/user.stub'; -import { getForAlbum, getForPartner } from 'test/mappers'; +import { getForPartner } from 'test/mappers'; import { newTestService, ServiceMocks } from 'test/utils'; describe(MapService.name, () => { @@ -82,15 +82,15 @@ describe(MapService.name, () => { }; mocks.partner.getAll.mockResolvedValue([]); mocks.map.getMapMarkers.mockResolvedValue([marker]); - mocks.album.getOwned.mockResolvedValue([getForAlbum(AlbumFactory.create())]); - mocks.album.getShared.mockResolvedValue([ - getForAlbum(AlbumFactory.from().albumUser({ userId: userStub.user1.id }).build()), - ]); + const album1 = AlbumFactory.create(); + const album2 = AlbumFactory.from().albumUser({ userId: userStub.user1.id }).build(); + mocks.album.getAllIds.mockResolvedValue([album1.id, album2.id]); const markers = await sut.getMapMarkers(auth, { withSharedAlbums: true }); expect(markers).toHaveLength(1); expect(markers[0]).toEqual(marker); + expect(mocks.album.getAllIds).toHaveBeenCalledWith(auth.user.id); }); }); diff --git a/server/src/services/map.service.ts b/server/src/services/map.service.ts index 94eca77a60..3a825697b4 100644 --- a/server/src/services/map.service.ts +++ b/server/src/services/map.service.ts @@ -13,15 +13,7 @@ export class MapService extends BaseService { userIds.push(...partnerIds); } - // TODO convert to SQL join - const albumIds: string[] = []; - if (options.withSharedAlbums) { - const [ownedAlbums, sharedAlbums] = await Promise.all([ - this.albumRepository.getOwned(auth.user.id), - this.albumRepository.getShared(auth.user.id), - ]); - albumIds.push(...ownedAlbums.map((album) => album.id), ...sharedAlbums.map((album) => album.id)); - } + const albumIds = options.withSharedAlbums ? await this.albumRepository.getAllIds(auth.user.id) : []; return this.mapRepository.getMapMarkers(userIds, albumIds, options); } diff --git a/server/src/services/media.service.spec.ts b/server/src/services/media.service.spec.ts index cb29598c10..994ba436ad 100644 --- a/server/src/services/media.service.spec.ts +++ b/server/src/services/media.service.spec.ts @@ -1,4 +1,4 @@ -import { NotNull, ShallowDehydrateObject } from 'kysely'; +import { ShallowDehydrateObject } from 'kysely'; import { OutputInfo } from 'sharp'; import { SystemConfig } from 'src/config'; import { Exif } from 'src/database'; @@ -1937,7 +1937,7 @@ describe(MediaService.name, () => { describe('handleVideoConversion', () => { let asset: ReturnType & { - videoStream: VideoStreamInfo & { timeBase: NotNull }; + videoStream: VideoStreamInfo & { timeBase: number }; audioStream: AudioStreamInfo | null; format: VideoFormat; }; @@ -2698,9 +2698,11 @@ describe(MediaService.name, () => { expect(mocks.media.transcode).not.toHaveBeenCalled(); }); - it('should set options for nvenc', async () => { + it('should set options for nvenc sw decode', async () => { mocks.assetJob.getForVideoConversion.mockResolvedValue({ ...asset, ...probeStub.matroskaContainer }); - mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { accel: TranscodeHardwareAcceleration.Nvenc } }); + mocks.systemMetadata.get.mockResolvedValue({ + ffmpeg: { accel: TranscodeHardwareAcceleration.Nvenc, accelDecode: false }, + }); await sut.handleVideoConversion({ id: 'video-id' }); expect(mocks.media.transcode).toHaveBeenCalledWith( '/original/path.ext', @@ -2758,7 +2760,7 @@ describe(MediaService.name, () => { '/original/path.ext', expect.any(String), expect.objectContaining({ - inputOptions: expect.arrayContaining(['-init_hw_device', 'cuda=cuda:0', '-filter_hw_device', 'cuda']), + inputOptions: expect.any(Array), outputOptions: expect.arrayContaining([expect.stringContaining('-multipass')]), twoPass: false, }), @@ -2775,7 +2777,7 @@ describe(MediaService.name, () => { '/original/path.ext', expect.any(String), expect.objectContaining({ - inputOptions: expect.arrayContaining(['-init_hw_device', 'cuda=cuda:0', '-filter_hw_device', 'cuda']), + inputOptions: expect.any(Array), outputOptions: expect.arrayContaining(['-cq:v', '23', '-maxrate', '10000k', '-bufsize', '6897k']), twoPass: false, }), @@ -2792,7 +2794,7 @@ describe(MediaService.name, () => { '/original/path.ext', expect.any(String), expect.objectContaining({ - inputOptions: expect.arrayContaining(['-init_hw_device', 'cuda=cuda:0', '-filter_hw_device', 'cuda']), + inputOptions: expect.any(Array), outputOptions: expect.not.stringContaining('-maxrate'), twoPass: false, }), @@ -2809,7 +2811,7 @@ describe(MediaService.name, () => { '/original/path.ext', expect.any(String), expect.objectContaining({ - inputOptions: expect.arrayContaining(['-init_hw_device', 'cuda=cuda:0', '-filter_hw_device', 'cuda']), + inputOptions: expect.any(Array), outputOptions: expect.not.arrayContaining([expect.stringContaining('-preset')]), twoPass: false, }), @@ -2824,7 +2826,7 @@ describe(MediaService.name, () => { '/original/path.ext', expect.any(String), expect.objectContaining({ - inputOptions: expect.arrayContaining(['-init_hw_device', 'cuda=cuda:0', '-filter_hw_device', 'cuda']), + inputOptions: expect.any(Array), outputOptions: expect.not.arrayContaining([expect.stringContaining('-multipass')]), twoPass: false, }), @@ -2894,10 +2896,10 @@ describe(MediaService.name, () => { ); }); - it('should set options for qsv', async () => { + it('should set options for qsv with sw decode', async () => { mocks.assetJob.getForVideoConversion.mockResolvedValue({ ...asset, ...probeStub.matroskaContainer }); mocks.systemMetadata.get.mockResolvedValue({ - ffmpeg: { accel: TranscodeHardwareAcceleration.Qsv, maxBitrate: '10000k' }, + ffmpeg: { accel: TranscodeHardwareAcceleration.Qsv, maxBitrate: '10000k', accelDecode: false }, }); await sut.handleVideoConversion({ id: 'video-id' }); expect(mocks.media.transcode).toHaveBeenCalledWith( @@ -2947,13 +2949,14 @@ describe(MediaService.name, () => { ); }); - it('should set options for qsv with custom dri node', async () => { + it('should set options for qsv with custom dri node with sw decode', async () => { mocks.assetJob.getForVideoConversion.mockResolvedValue({ ...asset, ...probeStub.matroskaContainer }); mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { accel: TranscodeHardwareAcceleration.Qsv, maxBitrate: '10000k', preferredHwDevice: '/dev/dri/renderD128', + accelDecode: false, }, }); await sut.handleVideoConversion({ id: 'video-id' }); @@ -2983,12 +2986,7 @@ describe(MediaService.name, () => { '/original/path.ext', expect.any(String), expect.objectContaining({ - inputOptions: expect.arrayContaining([ - '-init_hw_device', - 'qsv=hw,child_device=/dev/dri/renderD128', - '-filter_hw_device', - 'hw', - ]), + inputOptions: expect.any(Array), outputOptions: expect.not.arrayContaining([expect.stringContaining('-preset')]), twoPass: false, }), @@ -3005,12 +3003,7 @@ describe(MediaService.name, () => { '/original/path.ext', expect.any(String), expect.objectContaining({ - inputOptions: expect.arrayContaining([ - '-init_hw_device', - 'qsv=hw,child_device=/dev/dri/renderD128', - '-filter_hw_device', - 'hw', - ]), + inputOptions: expect.any(Array), outputOptions: expect.arrayContaining(['-low_power', '1']), twoPass: false, }), @@ -3036,12 +3029,7 @@ describe(MediaService.name, () => { '/original/path.ext', expect.any(String), expect.objectContaining({ - inputOptions: expect.arrayContaining([ - '-init_hw_device', - 'qsv=hw,child_device=/dev/dri/renderD129', - '-filter_hw_device', - 'hw', - ]), + inputOptions: expect.arrayContaining(['-qsv_device', '/dev/dri/renderD129']), outputOptions: expect.arrayContaining(['-c:v', 'h264_qsv']), twoPass: false, }), @@ -3158,9 +3146,11 @@ describe(MediaService.name, () => { ); }); - it('should set options for vaapi', async () => { + it('should set options for sw decode vaapi', async () => { mocks.assetJob.getForVideoConversion.mockResolvedValue({ ...asset, ...probeStub.matroskaContainer }); - mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { accel: TranscodeHardwareAcceleration.Vaapi } }); + mocks.systemMetadata.get.mockResolvedValue({ + ffmpeg: { accel: TranscodeHardwareAcceleration.Vaapi, accelDecode: false }, + }); await sut.handleVideoConversion({ id: 'video-id' }); expect(mocks.media.transcode).toHaveBeenCalledWith( '/original/path.ext', @@ -3211,12 +3201,7 @@ describe(MediaService.name, () => { '/original/path.ext', expect.any(String), expect.objectContaining({ - inputOptions: expect.arrayContaining([ - '-init_hw_device', - 'vaapi=accel:/dev/dri/renderD128', - '-filter_hw_device', - 'accel', - ]), + inputOptions: expect.any(Array), outputOptions: expect.arrayContaining([ '-c:v', 'h264_vaapi', @@ -3242,12 +3227,7 @@ describe(MediaService.name, () => { '/original/path.ext', expect.any(String), expect.objectContaining({ - inputOptions: expect.arrayContaining([ - '-init_hw_device', - 'vaapi=accel:/dev/dri/renderD128', - '-filter_hw_device', - 'accel', - ]), + inputOptions: expect.any(Array), outputOptions: expect.arrayContaining([ '-c:v', 'h264_vaapi', @@ -3275,12 +3255,7 @@ describe(MediaService.name, () => { '/original/path.ext', expect.any(String), expect.objectContaining({ - inputOptions: expect.arrayContaining([ - '-init_hw_device', - 'vaapi=accel:/dev/dri/renderD128', - '-filter_hw_device', - 'accel', - ]), + inputOptions: expect.any(Array), outputOptions: expect.not.arrayContaining([expect.stringContaining('-compression_level')]), twoPass: false, }), @@ -3296,12 +3271,7 @@ describe(MediaService.name, () => { '/original/path.ext', expect.any(String), expect.objectContaining({ - inputOptions: expect.arrayContaining([ - '-init_hw_device', - 'vaapi=accel:/dev/dri/renderD129', - '-filter_hw_device', - 'accel', - ]), + inputOptions: expect.arrayContaining(['-hwaccel_device', '/dev/dri/renderD129']), outputOptions: expect.arrayContaining(['-c:v', 'h264_vaapi']), twoPass: false, }), @@ -3319,12 +3289,7 @@ describe(MediaService.name, () => { '/original/path.ext', expect.any(String), expect.objectContaining({ - inputOptions: expect.arrayContaining([ - '-init_hw_device', - 'vaapi=accel:/dev/dri/renderD128', - '-filter_hw_device', - 'accel', - ]), + inputOptions: expect.arrayContaining(['-hwaccel_device', '/dev/dri/renderD128']), outputOptions: expect.arrayContaining(['-c:v', 'h264_vaapi']), twoPass: false, }), @@ -3481,7 +3446,9 @@ describe(MediaService.name, () => { it('should fallback to sw transcoding if hw transcoding fails and hw decoding is disabled', async () => { mocks.assetJob.getForVideoConversion.mockResolvedValue({ ...asset, ...probeStub.matroskaContainer }); - mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { accel: TranscodeHardwareAcceleration.Vaapi } }); + mocks.systemMetadata.get.mockResolvedValue({ + ffmpeg: { accel: TranscodeHardwareAcceleration.Vaapi, accelDecode: false }, + }); mocks.media.transcode.mockRejectedValueOnce(new Error('error')); await sut.handleVideoConversion({ id: 'video-id' }); expect(mocks.media.transcode).toHaveBeenCalledTimes(2); diff --git a/server/src/services/media.service.ts b/server/src/services/media.service.ts index 0b8a1f6702..a73eb3e22e 100644 --- a/server/src/services/media.service.ts +++ b/server/src/services/media.service.ts @@ -13,6 +13,7 @@ import { AudioCodec, Colorspace, ImageFormat, + ImmichWorker, JobName, JobStatus, QueueName, @@ -60,10 +61,9 @@ type ThumbnailAsset = NonNullable= targetSize; } - private async getDevices() { - try { - return await this.storageRepository.readdir('/dev/dri'); - } catch { - this.logger.debug('No devices found in /dev/dri.'); - return []; - } - } - - private async hasMaliOpenCL() { - try { - const [maliIcdStat, maliDeviceStat] = await Promise.all([ - this.storageRepository.stat('/etc/OpenCL/vendors/mali.icd'), - this.storageRepository.stat('/dev/mali0'), - ]); - return maliIcdStat.isFile() && maliDeviceStat.isCharacterDevice(); - } catch { - this.logger.debug('OpenCL not available for transcoding, so RKMPP acceleration will use CPU tonemapping'); - return false; - } - } - private async syncFiles( oldFiles: (AssetFile & { isProgressive: boolean; isTransparent: boolean })[], newFiles: UpsertFileOptions[], diff --git a/server/src/services/memory.service.spec.ts b/server/src/services/memory.service.spec.ts index 0445cf892b..9976189e39 100644 --- a/server/src/services/memory.service.spec.ts +++ b/server/src/services/memory.service.spec.ts @@ -28,25 +28,26 @@ describe(MemoryService.name, () => { }); describe('search', () => { - it('should search memories', async () => { + it('should search memories with assets', async () => { const [userId] = newUuids(); + const asset = AssetFactory.create(); const memory1 = MemoryFactory.from({ ownerId: userId }).asset(asset).build(); const memory2 = MemoryFactory.create({ ownerId: userId }); - mocks.memory.search.mockResolvedValue([getForMemory(memory1), getForMemory(memory2)]); await expect(sut.search(factory.auth({ user: { id: userId } }), {})).resolves.toEqual( expect.arrayContaining([ - expect.objectContaining({ id: memory1.id, assets: [expect.objectContaining({ id: asset.id })] }), - expect.objectContaining({ id: memory2.id, assets: [] }), + expect.objectContaining({ + id: memory1.id, + assets: expect.arrayContaining([expect.objectContaining({ id: asset.id })]), + }), ]), ); }); - it('should map ', async () => { + it('should map empty result', async () => { mocks.memory.search.mockResolvedValue([]); - await expect(sut.search(factory.auth(), {})).resolves.toEqual([]); }); }); diff --git a/server/src/services/memory.service.ts b/server/src/services/memory.service.ts index 2378d594e1..ac8f88ad87 100644 --- a/server/src/services/memory.service.ts +++ b/server/src/services/memory.service.ts @@ -1,5 +1,6 @@ import { BadRequestException, Injectable } from '@nestjs/common'; import { DateTime } from 'luxon'; +import { Memory } from 'src/database'; import { OnJob } from 'src/decorators'; import { BulkIdResponseDto, BulkIdsDto } from 'src/dtos/asset-ids.response.dto'; import { AuthDto } from 'src/dtos/auth.dto'; @@ -71,7 +72,9 @@ export class MemoryService extends BaseService { async search(auth: AuthDto, dto: MemorySearchDto) { const memories = await this.memoryRepository.search(auth.user.id, dto); - return memories.map((memory) => mapMemory(memory, auth)); + return memories + .filter((memory: Memory) => memory.assets && memory.assets.length > 0) + .map((memory: Memory) => mapMemory(memory, auth)); } statistics(auth: AuthDto, dto: MemorySearchDto) { diff --git a/server/src/services/metadata.service.spec.ts b/server/src/services/metadata.service.spec.ts index 19efd50e99..f5ffe52375 100644 --- a/server/src/services/metadata.service.spec.ts +++ b/server/src/services/metadata.service.spec.ts @@ -672,7 +672,7 @@ describe(MetadataService.name, () => { colorPrimaries: 9, colorTransfer: 16, colorMatrix: 9, - dvProfile: undefined, + dvProfile: null, }), }), ); diff --git a/server/src/services/person.service.spec.ts b/server/src/services/person.service.spec.ts index 8b303d04f6..e6a11786af 100644 --- a/server/src/services/person.service.spec.ts +++ b/server/src/services/person.service.spec.ts @@ -57,7 +57,6 @@ describe(PersonService.name, () => { ], }); expect(mocks.person.getAllForUser).toHaveBeenCalledWith({ skip: 0, take: 10 }, auth.user.id, { - minimumFaceCount: 3, withHidden: true, }); }); @@ -84,7 +83,6 @@ describe(PersonService.name, () => { ], }); expect(mocks.person.getAllForUser).toHaveBeenCalledWith({ skip: 0, take: 10 }, auth.user.id, { - minimumFaceCount: 3, withHidden: false, }); }); @@ -454,6 +452,30 @@ describe(PersonService.name, () => { expect(mocks.person.update).not.toHaveBeenCalled(); expect(mocks.job.queueAll).not.toHaveBeenCalled(); }); + + it('should reject creating a face on an asset the user does not own', async () => { + const auth = AuthFactory.create(); + const asset = AssetFactory.create(); + const person = PersonFactory.create({ faceAssetId: null }); + + mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set()); + mocks.access.person.checkOwnerAccess.mockResolvedValue(new Set([person.id])); + + await expect( + sut.createFace(auth, { + assetId: asset.id, + personId: person.id, + imageHeight: 500, + imageWidth: 400, + x: 10, + y: 20, + width: 100, + height: 110, + }), + ).rejects.toBeInstanceOf(BadRequestException); + + expect(mocks.person.createAssetFace).not.toHaveBeenCalled(); + }); }); describe('createNewFeaturePhoto', () => { diff --git a/server/src/services/person.service.ts b/server/src/services/person.service.ts index fde5313f4d..bb17590795 100644 --- a/server/src/services/person.service.ts +++ b/server/src/services/person.service.ts @@ -63,9 +63,7 @@ export class PersonService extends BaseService { } closestFaceAssetId = person.faceAssetId; } - const { machineLearning } = await this.getConfig({ withCache: false }); const { items, hasNextPage } = await this.personRepository.getAllForUser(pagination, auth.user.id, { - minimumFaceCount: machineLearning.facialRecognition.minFaces, withHidden, closestFaceAssetId, }); @@ -627,7 +625,7 @@ export class PersonService extends BaseService { // TODO return a asset face response async createFace(auth: AuthDto, dto: AssetFaceCreateDto): Promise { await Promise.all([ - this.requireAccess({ auth, permission: Permission.AssetRead, ids: [dto.assetId] }), + this.requireAccess({ auth, permission: Permission.AssetUpdate, ids: [dto.assetId] }), this.requireAccess({ auth, permission: Permission.PersonRead, ids: [dto.personId] }), ]); diff --git a/server/src/services/plugin-host.functions.ts b/server/src/services/plugin-host.functions.ts deleted file mode 100644 index 50b1052b54..0000000000 --- a/server/src/services/plugin-host.functions.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { CurrentPlugin } from '@extism/extism'; -import { UnauthorizedException } from '@nestjs/common'; -import { Updateable } from 'kysely'; -import { Permission } from 'src/enum'; -import { AccessRepository } from 'src/repositories/access.repository'; -import { AlbumRepository } from 'src/repositories/album.repository'; -import { AssetRepository } from 'src/repositories/asset.repository'; -import { CryptoRepository } from 'src/repositories/crypto.repository'; -import { LoggingRepository } from 'src/repositories/logging.repository'; -import { AssetTable } from 'src/schema/tables/asset.table'; -import { requireAccess } from 'src/utils/access'; - -/** - * Plugin host functions that are exposed to WASM plugins via Extism. - * These functions allow plugins to interact with the Immich system. - */ -export class PluginHostFunctions { - constructor( - private assetRepository: AssetRepository, - private albumRepository: AlbumRepository, - private accessRepository: AccessRepository, - private cryptoRepository: CryptoRepository, - private logger: LoggingRepository, - private pluginJwtSecret: string, - ) {} - - /** - * Creates Extism host function bindings for the plugin. - * These are the functions that WASM plugins can call. - */ - getHostFunctions() { - return { - 'extism:host/user': { - updateAsset: (cp: CurrentPlugin, offs: bigint) => this.handleUpdateAsset(cp, offs), - addAssetToAlbum: (cp: CurrentPlugin, offs: bigint) => this.handleAddAssetToAlbum(cp, offs), - }, - }; - } - - /** - * Host function wrapper for updateAsset. - * Reads the input from the plugin, parses it, and calls the actual update function. - */ - private async handleUpdateAsset(cp: CurrentPlugin, offs: bigint) { - const input = JSON.parse(cp.read(offs)!.text()); - await this.updateAsset(input); - } - - /** - * Host function wrapper for addAssetToAlbum. - * Reads the input from the plugin, parses it, and calls the actual add function. - */ - private async handleAddAssetToAlbum(cp: CurrentPlugin, offs: bigint) { - const input = JSON.parse(cp.read(offs)!.text()); - await this.addAssetToAlbum(input); - } - - /** - * Validates the JWT token and returns the auth context. - */ - private validateToken(authToken: string): { userId: string } { - try { - const auth = this.cryptoRepository.verifyJwt<{ userId: string }>(authToken, this.pluginJwtSecret); - if (!auth.userId) { - throw new UnauthorizedException('Invalid token: missing userId'); - } - return auth; - } catch (error) { - this.logger.error('Token validation failed:', error); - throw new UnauthorizedException('Invalid token'); - } - } - - /** - * Updates an asset with the given properties. - */ - async updateAsset(input: { authToken: string } & Updateable & { id: string }) { - const { authToken, id, ...assetData } = input; - - // Validate token - const auth = this.validateToken(authToken); - - // Check access to the asset - await requireAccess(this.accessRepository, { - auth: { user: { id: auth.userId } } as any, - permission: Permission.AssetUpdate, - ids: [id], - }); - - this.logger.log(`Updating asset ${id} -- ${JSON.stringify(assetData)}`); - await this.assetRepository.update({ id, ...assetData }); - } - - /** - * Adds an asset to an album. - */ - async addAssetToAlbum(input: { authToken: string; assetId: string; albumId: string }) { - const { authToken, assetId, albumId } = input; - - // Validate token - const auth = this.validateToken(authToken); - - // Check access to both the asset and the album - await requireAccess(this.accessRepository, { - auth: { user: { id: auth.userId } } as any, - permission: Permission.AssetRead, - ids: [assetId], - }); - - await requireAccess(this.accessRepository, { - auth: { user: { id: auth.userId } } as any, - permission: Permission.AlbumUpdate, - ids: [albumId], - }); - - this.logger.log(`Adding asset ${assetId} to album ${albumId}`); - await this.albumRepository.addAssetIds(albumId, [assetId]); - return 0; - } -} diff --git a/server/src/services/plugin.service.ts b/server/src/services/plugin.service.ts index 7209a613fe..f9c4d0e2b8 100644 --- a/server/src/services/plugin.service.ts +++ b/server/src/services/plugin.service.ts @@ -1,313 +1,41 @@ -import { Plugin as ExtismPlugin, newPlugin } from '@extism/extism'; import { BadRequestException, Injectable } from '@nestjs/common'; -import { join } from 'node:path'; -import { Asset, WorkflowAction, WorkflowFilter } from 'src/database'; -import { OnEvent, OnJob } from 'src/decorators'; -import { PluginManifestDto, PluginManifestSchema } from 'src/dtos/plugin-manifest.dto'; -import { mapPlugin, PluginResponseDto, PluginTriggerResponseDto } from 'src/dtos/plugin.dto'; -import { JobName, JobStatus, PluginTriggerType, QueueName } from 'src/enum'; -import { pluginTriggers } from 'src/plugins'; -import { ArgOf } from 'src/repositories/event.repository'; +import { + mapMethod, + mapPlugin, + mapTemplate, + PluginMethodResponseDto, + PluginMethodSearchDto, + PluginResponseDto, + PluginSearchDto, + PluginTemplateResponseDto, +} from 'src/dtos/plugin.dto'; import { BaseService } from 'src/services/base.service'; -import { PluginHostFunctions } from 'src/services/plugin-host.functions'; -import { IWorkflowJob, JobItem, JobOf, WorkflowData } from 'src/types'; - -interface WorkflowContext { - authToken: string; - asset: Asset; -} - -interface PluginInput { - authToken: string; - config: T; - data: { - asset: Asset; - }; -} +import { isMethodCompatible } from 'src/utils/workflow'; @Injectable() export class PluginService extends BaseService { - private pluginJwtSecret!: string; - private loadedPlugins: Map = new Map(); - private hostFunctions!: PluginHostFunctions; - - @OnEvent({ name: 'AppBootstrap' }) - async onBootstrap() { - this.pluginJwtSecret = this.cryptoRepository.randomBytesAsText(32); - - await this.loadPluginsFromManifests(); - - this.hostFunctions = new PluginHostFunctions( - this.assetRepository, - this.albumRepository, - this.accessRepository, - this.cryptoRepository, - this.logger, - this.pluginJwtSecret, - ); - - await this.loadPlugins(); - } - - getTriggers(): PluginTriggerResponseDto[] { - return pluginTriggers; - } - - // - // CRUD operations for plugins - // - async getAll(): Promise { - const plugins = await this.pluginRepository.getAllPlugins(); + async search(dto: PluginSearchDto): Promise { + const plugins = await this.pluginRepository.search(dto); return plugins.map((plugin) => mapPlugin(plugin)); } async get(id: string): Promise { - const plugin = await this.pluginRepository.getPlugin(id); + const plugin = await this.pluginRepository.get(id); if (!plugin) { throw new BadRequestException('Plugin not found'); } return mapPlugin(plugin); } - /////////////////////////////////////////// - // Plugin Loader - ////////////////////////////////////////// - async loadPluginsFromManifests(): Promise { - // Load core plugin - const { resourcePaths, plugins } = this.configRepository.getEnv(); - const coreManifestPath = `${resourcePaths.corePlugin}/manifest.json`; - - const coreManifest = await this.readAndValidateManifest(coreManifestPath); - await this.loadPluginToDatabase(coreManifest, resourcePaths.corePlugin); - - this.logger.log(`Successfully processed core plugin: ${coreManifest.name} (version ${coreManifest.version})`); - - // Load external plugins - if (plugins.external.allow && plugins.external.installFolder) { - await this.loadExternalPlugins(plugins.external.installFolder); - } + async searchMethods(dto: PluginMethodSearchDto): Promise { + const methods = await this.pluginRepository.searchMethods(dto); + return methods + .filter((method) => !dto.trigger || isMethodCompatible(method, dto.trigger)) + .map((method) => mapMethod(method)); } - private async loadExternalPlugins(installFolder: string): Promise { - try { - const entries = await this.pluginRepository.readDirectory(installFolder); - - for (const entry of entries) { - if (!entry.isDirectory()) { - continue; - } - - const pluginFolder = join(installFolder, entry.name); - const manifestPath = join(pluginFolder, 'manifest.json'); - try { - const manifest = await this.readAndValidateManifest(manifestPath); - await this.loadPluginToDatabase(manifest, pluginFolder); - - this.logger.log(`Successfully processed external plugin: ${manifest.name} (version ${manifest.version})`); - } catch (error) { - this.logger.warn(`Failed to load external plugin from ${manifestPath}:`, error); - } - } - } catch (error) { - this.logger.error(`Failed to scan external plugins folder ${installFolder}:`, error); - } - } - - private async loadPluginToDatabase(manifest: PluginManifestDto, basePath: string): Promise { - const currentPlugin = await this.pluginRepository.getPluginByName(manifest.name); - if (currentPlugin != null && currentPlugin.version === manifest.version) { - this.logger.log(`Plugin ${manifest.name} is up to date (version ${manifest.version}). Skipping`); - return; - } - - const { plugin, filters, actions } = await this.pluginRepository.loadPlugin(manifest, basePath); - - this.logger.log(`Upserted plugin: ${plugin.name} (ID: ${plugin.id}, version: ${plugin.version})`); - - for (const filter of filters) { - this.logger.log(`Upserted plugin filter: ${filter.methodName} (ID: ${filter.id})`); - } - - for (const action of actions) { - this.logger.log(`Upserted plugin action: ${action.methodName} (ID: ${action.id})`); - } - } - - private async readAndValidateManifest(manifestPath: string): Promise { - const content = await this.storageRepository.readTextFile(manifestPath); - const manifestData = JSON.parse(content); - return PluginManifestSchema.parse(manifestData); - } - - /////////////////////////////////////////// - // Plugin Execution - /////////////////////////////////////////// - private async loadPlugins() { - const plugins = await this.pluginRepository.getAllPlugins(); - for (const plugin of plugins) { - try { - this.logger.debug(`Loading plugin: ${plugin.name} from ${plugin.wasmPath}`); - - const extismPlugin = await newPlugin(plugin.wasmPath, { - useWasi: true, - functions: this.hostFunctions.getHostFunctions(), - }); - - this.loadedPlugins.set(plugin.id, extismPlugin); - this.logger.log(`Successfully loaded plugin: ${plugin.name}`); - } catch (error) { - this.logger.error(`Failed to load plugin ${plugin.name}:`, error); - } - } - } - - @OnEvent({ name: 'AssetCreate' }) - async handleAssetCreate({ asset }: ArgOf<'AssetCreate'>) { - await this.handleTrigger(PluginTriggerType.AssetCreate, { - ownerId: asset.ownerId, - event: { userId: asset.ownerId, asset }, - }); - } - - private async handleTrigger( - triggerType: T, - params: { ownerId: string; event: WorkflowData[T] }, - ): Promise { - const workflows = await this.workflowRepository.getWorkflowByOwnerAndTrigger(params.ownerId, triggerType); - if (workflows.length === 0) { - return; - } - - const jobs: JobItem[] = workflows.map((workflow) => ({ - name: JobName.WorkflowRun, - data: { - id: workflow.id, - type: triggerType, - event: params.event, - } as IWorkflowJob, - })); - - await this.jobRepository.queueAll(jobs); - this.logger.debug(`Queued ${jobs.length} workflow execution jobs for trigger ${triggerType}`); - } - - @OnJob({ name: JobName.WorkflowRun, queue: QueueName.Workflow }) - async handleWorkflowRun({ id: workflowId, type, event }: JobOf): Promise { - try { - const workflow = await this.workflowRepository.getWorkflow(workflowId); - if (!workflow) { - this.logger.error(`Workflow ${workflowId} not found`); - return JobStatus.Failed; - } - - const workflowFilters = await this.workflowRepository.getFilters(workflowId); - const workflowActions = await this.workflowRepository.getActions(workflowId); - - switch (type) { - case PluginTriggerType.AssetCreate: { - const data = event as WorkflowData[PluginTriggerType.AssetCreate]; - const asset = data.asset; - - const authToken = this.cryptoRepository.signJwt({ userId: data.userId }, this.pluginJwtSecret); - - const context = { - authToken, - asset, - }; - - const filtersPassed = await this.executeFilters(workflowFilters, context); - if (!filtersPassed) { - return JobStatus.Skipped; - } - - await this.executeActions(workflowActions, context); - this.logger.debug(`Workflow ${workflowId} executed successfully`); - return JobStatus.Success; - } - - case PluginTriggerType.PersonRecognized: { - this.logger.error('unimplemented'); - return JobStatus.Skipped; - } - - default: { - this.logger.error(`Unknown workflow trigger type: ${type}`); - return JobStatus.Failed; - } - } - } catch (error) { - this.logger.error(`Error executing workflow ${workflowId}:`, error); - return JobStatus.Failed; - } - } - - private async executeFilters(workflowFilters: WorkflowFilter[], context: WorkflowContext): Promise { - for (const workflowFilter of workflowFilters) { - const filter = await this.pluginRepository.getFilter(workflowFilter.pluginFilterId); - if (!filter) { - this.logger.error(`Filter ${workflowFilter.pluginFilterId} not found`); - return false; - } - - const pluginInstance = this.loadedPlugins.get(filter.pluginId); - if (!pluginInstance) { - this.logger.error(`Plugin ${filter.pluginId} not loaded`); - return false; - } - - const filterInput: PluginInput = { - authToken: context.authToken, - config: workflowFilter.filterConfig, - data: { - asset: context.asset, - }, - }; - - this.logger.debug(`Calling filter ${filter.methodName} with input: ${JSON.stringify(filterInput)}`); - - const filterResult = await pluginInstance.call( - filter.methodName, - new TextEncoder().encode(JSON.stringify(filterInput)), - ); - - if (!filterResult) { - this.logger.error(`Filter ${filter.methodName} returned null`); - return false; - } - - const result = JSON.parse(filterResult.text()); - if (result.passed === false) { - this.logger.debug(`Filter ${filter.methodName} returned false, stopping workflow execution`); - return false; - } - } - - return true; - } - - private async executeActions(workflowActions: WorkflowAction[], context: WorkflowContext): Promise { - for (const workflowAction of workflowActions) { - const action = await this.pluginRepository.getAction(workflowAction.pluginActionId); - if (!action) { - throw new Error(`Action ${workflowAction.pluginActionId} not found`); - } - - const pluginInstance = this.loadedPlugins.get(action.pluginId); - if (!pluginInstance) { - throw new Error(`Plugin ${action.pluginId} not loaded`); - } - - const actionInput: PluginInput = { - authToken: context.authToken, - config: workflowAction.actionConfig, - data: { - asset: context.asset, - }, - }; - - this.logger.debug(`Calling action ${action.methodName} with input: ${JSON.stringify(actionInput)}`); - - await pluginInstance.call(action.methodName, JSON.stringify(actionInput)); - } + async searchTemplates(): Promise { + const plugins = await this.pluginRepository.search(); + return plugins.flatMap((plugin) => plugin.templates.map((template) => mapTemplate(plugin, template))); } } diff --git a/server/src/services/queue.service.spec.ts b/server/src/services/queue.service.spec.ts index d4c425e8bd..48c61c0951 100644 --- a/server/src/services/queue.service.spec.ts +++ b/server/src/services/queue.service.spec.ts @@ -41,6 +41,7 @@ describe(QueueService.name, () => { { name: JobName.PersonCleanup }, { name: JobName.MemoryCleanup }, { name: JobName.SessionCleanup }, + { name: JobName.HlsSessionCleanup }, { name: JobName.AuditTableCleanup }, { name: JobName.MemoryGenerate }, { name: JobName.UserSyncUsage }, diff --git a/server/src/services/queue.service.ts b/server/src/services/queue.service.ts index ba6f4c5f3b..d11c9180b2 100644 --- a/server/src/services/queue.service.ts +++ b/server/src/services/queue.service.ts @@ -269,6 +269,7 @@ export class QueueService extends BaseService { { name: JobName.PersonCleanup }, { name: JobName.MemoryCleanup }, { name: JobName.SessionCleanup }, + { name: JobName.HlsSessionCleanup }, { name: JobName.AuditTableCleanup }, ); } diff --git a/server/src/services/search.service.spec.ts b/server/src/services/search.service.spec.ts index f1cbccb7ec..4680b4c9de 100644 --- a/server/src/services/search.service.spec.ts +++ b/server/src/services/search.service.spec.ts @@ -65,7 +65,7 @@ describe(SearchService.name, () => { }); describe('getExploreData', () => { - it('should get assets by city and tag', async () => { + it('should get recent assets and assets by city and tag', async () => { const auth = AuthFactory.create(); const asset = AssetFactory.from() .exif({ latitude: 42, longitude: 69, city: 'city', state: 'state', country: 'country' }) @@ -74,9 +74,17 @@ describe(SearchService.name, () => { fieldName: 'exifInfo.city', items: [{ value: 'city', data: asset.id }], }); + mocks.asset.getRecentlyCreatedAssetIds.mockResolvedValue({ + fieldName: 'createdAt', + items: [{ value: asset.createdAt, data: asset.id }], + }); mocks.asset.getByIdsWithAllRelationsButStacks.mockResolvedValue([asset as never]); const expectedResponse = [ { fieldName: 'exifInfo.city', items: [{ value: 'city', data: mapAsset(getForAsset(asset)) }] }, + { + fieldName: 'createdAt', + items: [{ value: asset.createdAt.toISOString(), data: mapAsset(getForAsset(asset)) }], + }, ]; const result = await sut.getExploreData(auth); diff --git a/server/src/services/search.service.ts b/server/src/services/search.service.ts index 9a6f8321a9..bd50ab3083 100644 --- a/server/src/services/search.service.ts +++ b/server/src/services/search.service.ts @@ -40,10 +40,26 @@ export class SearchService extends BaseService { async getExploreData(auth: AuthDto) { const options = { maxFields: 12, minAssetsPerField: 5 }; + const cities = await this.assetRepository.getAssetIdByCity(auth.user.id, options); - const assets = await this.assetRepository.getByIdsWithAllRelationsButStacks(cities.items.map(({ data }) => data)); - const items = assets.map((asset) => ({ value: asset.exifInfo!.city!, data: mapAsset(asset, { auth }) })); - return [{ fieldName: cities.fieldName, items }]; + const cityAssets = await this.assetRepository.getByIdsWithAllRelationsButStacks( + cities.items.map(({ data }) => data), + ); + const cityItems = cityAssets.map((asset) => ({ value: asset.exifInfo!.city!, data: mapAsset(asset, { auth }) })); + + const recents = await this.assetRepository.getRecentlyCreatedAssetIds(auth.user.id, options.maxFields); + const recentAssets = await this.assetRepository.getByIdsWithAllRelationsButStacks( + recents.items.map((item) => item.data), + ); + const recentItems = recentAssets.map((asset) => ({ + value: asset.createdAt.toISOString(), + data: mapAsset(asset, { auth }), + })); + + return [ + { fieldName: cities.fieldName, items: cityItems }, + { fieldName: recents.fieldName, items: recentItems }, + ]; } async searchMetadata(auth: AuthDto, dto: MetadataSearchDto): Promise { @@ -59,7 +75,7 @@ export class SearchService extends BaseService { const page = dto.page ?? 1; const size = dto.size || 250; - const userIds = await this.getUserIdsToSearch(auth); + const userIds = await this.getUserIdsToSearch(auth, dto.visibility); const { hasNextPage, items } = await this.searchRepository.searchMetadata( { page, size }, { @@ -87,7 +103,7 @@ export class SearchService extends BaseService { requireElevatedPermission(auth); } - const userIds = await this.getUserIdsToSearch(auth); + const userIds = await this.getUserIdsToSearch(auth, dto.visibility); const items = await this.searchRepository.searchRandom(dto.size || 250, { ...dto, userIds }); return items.map((item) => mapAsset(item, { auth })); } @@ -97,7 +113,7 @@ export class SearchService extends BaseService { requireElevatedPermission(auth); } - const userIds = await this.getUserIdsToSearch(auth); + const userIds = await this.getUserIdsToSearch(auth, dto.visibility); const items = await this.searchRepository.searchLargeAssets(dto.size || 250, { ...dto, userIds }); return items.map((item) => mapAsset(item, { auth })); } @@ -112,7 +128,7 @@ export class SearchService extends BaseService { throw new BadRequestException('Smart search is not enabled'); } - const userIds = this.getUserIdsToSearch(auth); + const userIds = this.getUserIdsToSearch(auth, dto.visibility); let embedding; if (dto.query) { const key = machineLearning.clip.modelName + dto.query + dto.language; @@ -186,7 +202,11 @@ export class SearchService extends BaseService { } } - private async getUserIdsToSearch(auth: AuthDto): Promise { + private async getUserIdsToSearch(auth: AuthDto, visibility?: AssetVisibility): Promise { + // Locked assets are personal. Never include partner IDs, regardless of A's elevated session. + if (visibility === AssetVisibility.Locked) { + return [auth.user.id]; + } const partnerIds = await getMyPartnerIds({ userId: auth.user.id, repository: this.partnerRepository, diff --git a/server/src/services/server.service.spec.ts b/server/src/services/server.service.spec.ts index 6e1187a900..e1575a496a 100644 --- a/server/src/services/server.service.spec.ts +++ b/server/src/services/server.service.spec.ts @@ -148,6 +148,7 @@ describe(ServerService.name, () => { configFile: false, trash: true, email: false, + realtimeTranscoding: false, }); expect(mocks.systemMetadata.get).toHaveBeenCalled(); }); @@ -167,6 +168,7 @@ describe(ServerService.name, () => { mapDarkStyleUrl: 'https://tiles.immich.cloud/v1/style/dark.json', mapLightStyleUrl: 'https://tiles.immich.cloud/v1/style/light.json', maintenanceMode: false, + minFaces: 3, }); expect(mocks.systemMetadata.get).toHaveBeenCalled(); }); diff --git a/server/src/services/server.service.ts b/server/src/services/server.service.ts index ff9e90f7e0..3b66b677a5 100644 --- a/server/src/services/server.service.ts +++ b/server/src/services/server.service.ts @@ -86,7 +86,7 @@ export class ServerService extends BaseService { } async getFeatures(): Promise { - const { reverseGeocoding, metadata, map, machineLearning, trash, oauth, passwordLogin, notifications } = + const { reverseGeocoding, metadata, map, machineLearning, trash, oauth, passwordLogin, notifications, ffmpeg } = await this.getConfig({ withCache: false }); const { configFile } = this.configRepository.getEnv(); @@ -106,6 +106,7 @@ export class ServerService extends BaseService { passwordLogin: passwordLogin.enabled, configFile: !!configFile, email: notifications.smtp.enabled, + realtimeTranscoding: ffmpeg.realtime.enabled, }; } @@ -127,6 +128,7 @@ export class ServerService extends BaseService { mapDarkStyleUrl: config.map.darkStyle, mapLightStyleUrl: config.map.lightStyle, maintenanceMode: false, + minFaces: config.machineLearning.facialRecognition.minFaces, }; } diff --git a/server/src/services/shared-link.service.ts b/server/src/services/shared-link.service.ts index 0643a432b8..79e4b23e6a 100644 --- a/server/src/services/shared-link.service.ts +++ b/server/src/services/shared-link.service.ts @@ -124,7 +124,7 @@ export class SharedLinkService extends BaseService { userId: auth.user.id, description: dto.description, password: dto.password, - expiresAt: dto.changeExpiryTime && !dto.expiresAt ? null : dto.expiresAt, + expiresAt: dto.expiresAt, allowUpload: dto.allowUpload, allowDownload: dto.allowDownload, showExif: dto.showMetadata, diff --git a/server/src/services/system-config.service.spec.ts b/server/src/services/system-config.service.spec.ts index fb07eb1438..2d0850ac58 100644 --- a/server/src/services/system-config.service.spec.ts +++ b/server/src/services/system-config.service.spec.ts @@ -1,5 +1,6 @@ import { BadRequestException } from '@nestjs/common'; import { defaults, SystemConfig } from 'src/config'; +import { ReleaseChannel } from 'src/dtos/system-config.dto'; import { AudioCodec, Colorspace, @@ -70,8 +71,11 @@ const updatedConfig = Object.freeze({ preferredHwDevice: 'auto', transcode: TranscodePolicy.Required, accel: TranscodeHardwareAcceleration.Disabled, - accelDecode: false, + accelDecode: true, tonemap: ToneMapping.Hable, + realtime: { + enabled: false, + }, }, logging: { enabled: true, @@ -184,6 +188,7 @@ const updatedConfig = Object.freeze({ }, newVersionCheck: { enabled: true, + channel: ReleaseChannel.Stable, }, trash: { enabled: true, diff --git a/server/src/services/timeline.service.spec.ts b/server/src/services/timeline.service.spec.ts index 4f447f6c3d..ca668b6068 100644 --- a/server/src/services/timeline.service.spec.ts +++ b/server/src/services/timeline.service.spec.ts @@ -204,5 +204,16 @@ describe(TimelineService.name, () => { }), ).rejects.toThrow(BadRequestException); }); + + it('should throw an error if withPartners is true and visibility is locked', async () => { + await expect( + sut.getTimeBucket(authStub.adminWithElevatedPermission, { + timeBucket: 'bucket', + visibility: AssetVisibility.Locked, + withPartners: true, + userId: authStub.adminWithElevatedPermission.user.id, + }), + ).rejects.toThrow(BadRequestException); + }); }); }); diff --git a/server/src/services/timeline.service.ts b/server/src/services/timeline.service.ts index b840883fa9..b7ecbad9a9 100644 --- a/server/src/services/timeline.service.ts +++ b/server/src/services/timeline.service.ts @@ -66,14 +66,19 @@ export class TimelineService extends BaseService { await this.requireAccess({ auth, permission: Permission.TagRead, ids: [dto.tagId] }); } + if (auth.sharedLink && !auth.sharedLink.showExif) { + dto.withCoordinates = false; + } + if (dto.withPartners) { + const requestedLocked = dto.visibility === AssetVisibility.Locked; const requestedArchived = dto.visibility === AssetVisibility.Archive || dto.visibility === undefined; const requestedFavorite = dto.isFavorite === true || dto.isFavorite === false; const requestedTrash = dto.isTrashed === true; - if (requestedArchived || requestedFavorite || requestedTrash) { + if (requestedLocked || requestedArchived || requestedFavorite || requestedTrash) { throw new BadRequestException( - 'withPartners is only supported for non-archived, non-trashed, non-favorited assets', + 'withPartners is only supported for non-archived, non-trashed, non-favorited, non-locked assets', ); } } diff --git a/server/src/services/transcoding.service.spec.ts b/server/src/services/transcoding.service.spec.ts new file mode 100644 index 0000000000..349cba6a7d --- /dev/null +++ b/server/src/services/transcoding.service.spec.ts @@ -0,0 +1,539 @@ +import { + HLS_BACKPRESSURE_PAUSE_SEGMENTS, + HLS_BACKPRESSURE_RESUME_SEGMENTS, + HLS_CLEANUP_INTERVAL_MS, + HLS_INACTIVITY_TIMEOUT_MS, + HLS_LEASE_DURATION_MS, +} from 'src/constants'; +import { TranscodingService } from 'src/services/transcoding.service'; +import { VIDEO_STREAM_SESSION_PK_CONSTRAINT } from 'src/utils/database'; +import { eiffelTower, train, waterfall } from 'test/fixtures/media.stub'; +import { mockSpawn, newTestService, ServiceMocks } from 'test/utils'; +import { vi } from 'vitest'; + +describe(TranscodingService.name, () => { + let sut: TranscodingService; + let mocks: ServiceMocks; + + const sessionId = 'session-1'; + const assetId = 'asset-1'; + const ownerId = 'user-1'; + + const completeSegment = (index: number) => { + const listener = vi.mocked(mocks.storage.watchDir).mock.lastCall?.[1]; + expect(listener).toBeDefined(); + listener!('rename', `seg_${index}.m4s`); + }; + + const completeSegmentsThrough = (start: number, end: number) => { + for (let i = start; i <= end; i++) { + completeSegment(i); + } + }; + + beforeEach(() => { + ({ sut, mocks } = newTestService(TranscodingService)); + mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { realtime: { enabled: true } } }); + mocks.videoStream.getForTranscoding.mockResolvedValue(eiffelTower); + }); + + describe('onSessionRequest', () => { + it('creates the session row and emits HlsSessionResult on success', async () => { + await sut.onSessionRequest({ sessionId, assetId, ownerId }); + + expect(mocks.videoStream.createSession).toHaveBeenCalledWith({ + id: sessionId, + assetId, + expiresAt: expect.any(Date), + }); + expect(mocks.websocket.serverSend).toHaveBeenCalledWith('HlsSessionResult', { sessionId }); + }); + + it('treats a primary-key conflict as a no-op for replay tolerance', async () => { + mocks.videoStream.createSession.mockRejectedValue({ constraint_name: VIDEO_STREAM_SESSION_PK_CONSTRAINT }); + + await sut.onSessionRequest({ sessionId, assetId, ownerId }); + + expect(mocks.websocket.serverSend).not.toHaveBeenCalled(); + }); + + it('emits HlsSessionResult with an error on other DB failures', async () => { + mocks.videoStream.createSession.mockRejectedValue(new Error('database is down')); + + await sut.onSessionRequest({ sessionId, assetId, ownerId }); + + expect(mocks.websocket.serverSend).toHaveBeenCalledWith('HlsSessionResult', { + sessionId, + error: 'Failed to create HLS session', + }); + }); + }); + + describe('onSessionEnd', () => { + it('removes the session, kills the transcode, and deletes the dir + DB row', async () => { + await sut.onSessionRequest({ sessionId, assetId, ownerId }); + const process = mockSpawn(0, '', ''); + mocks.process.spawn.mockReturnValue(process); + await sut.onSegmentRequest({ sessionId, assetId, variantIndex: 0, segmentIndex: 0 }); + + await sut.onSessionEnd({ sessionId }); + + expect(process.kill).toHaveBeenCalled(); + expect(mocks.storage.unlinkDir).toHaveBeenCalled(); + expect(mocks.videoStream.deleteSession).toHaveBeenCalledWith(sessionId); + }); + + it('is a no-op when the session is unknown', async () => { + await sut.onSessionEnd({ sessionId: 'never-created' }); + + expect(mocks.videoStream.deleteSession).not.toHaveBeenCalled(); + expect(mocks.storage.unlinkDir).not.toHaveBeenCalled(); + }); + }); + + describe('onHeartbeat', () => { + it('extends the DB lease when remaining time falls below half', async () => { + vi.useFakeTimers(); + try { + await sut.onSessionRequest({ sessionId, assetId, ownerId }); + vi.setSystemTime(Date.now() + HLS_LEASE_DURATION_MS / 2 + 1); + + await sut.onHeartbeat({ sessionId }); + + expect(mocks.videoStream.extendSession).toHaveBeenCalledWith(sessionId, expect.any(Date)); + } finally { + vi.useRealTimers(); + } + }); + + it('does not extend the lease while it is still fresh', async () => { + await sut.onSessionRequest({ sessionId, assetId, ownerId }); + + await sut.onHeartbeat({ sessionId }); + + expect(mocks.videoStream.extendSession).not.toHaveBeenCalled(); + }); + + it('is a no-op when the session is unknown', async () => { + await sut.onHeartbeat({ sessionId: 'never-created' }); + + expect(mocks.videoStream.extendSession).not.toHaveBeenCalled(); + }); + }); + + describe('onSegmentRequest', () => { + beforeEach(async () => { + await sut.onSessionRequest({ sessionId, assetId, ownerId }); + mocks.websocket.serverSend.mockClear(); + }); + + it('spawns FFmpeg on the first request', async () => { + mocks.process.spawn.mockReturnValue(mockSpawn(0, '', '')); + + await sut.onSegmentRequest({ sessionId, assetId, variantIndex: 0, segmentIndex: 0 }); + + expect(mocks.process.spawn).toHaveBeenCalledTimes(1); + expect(mocks.process.spawn).toHaveBeenCalledWith('ffmpeg', expect.any(Array), expect.any(Object)); + }); + + it('kills and respawns when the variant changes', async () => { + const first = mockSpawn(0, '', ''); + const second = mockSpawn(0, '', ''); + mocks.process.spawn.mockReturnValueOnce(first).mockReturnValueOnce(second); + + await sut.onSegmentRequest({ sessionId, assetId, variantIndex: 0, segmentIndex: 0 }); + await sut.onSegmentRequest({ sessionId, assetId, variantIndex: 1, segmentIndex: 0 }); + + expect(first.kill).toHaveBeenCalled(); + expect(mocks.process.spawn).toHaveBeenCalledTimes(2); + }); + + it('kills and respawns when seeking before the start segment', async () => { + const first = mockSpawn(0, '', ''); + const second = mockSpawn(0, '', ''); + mocks.process.spawn.mockReturnValueOnce(first).mockReturnValueOnce(second); + + await sut.onSegmentRequest({ sessionId, assetId, variantIndex: 0, segmentIndex: 5 }); + await sut.onSegmentRequest({ sessionId, assetId, variantIndex: 0, segmentIndex: 2 }); + + expect(first.kill).toHaveBeenCalled(); + expect(mocks.process.spawn).toHaveBeenCalledTimes(2); + }); + + it('kills and respawns when the requested segment is too far ahead', async () => { + const first = mockSpawn(0, '', ''); + const second = mockSpawn(0, '', ''); + mocks.process.spawn.mockReturnValueOnce(first).mockReturnValueOnce(second); + + await sut.onSegmentRequest({ sessionId, assetId, variantIndex: 0, segmentIndex: 0 }); + await sut.onSegmentRequest({ sessionId, assetId, variantIndex: 0, segmentIndex: 5 }); + + expect(first.kill).toHaveBeenCalled(); + expect(mocks.process.spawn).toHaveBeenCalledTimes(2); + }); + + it('does not spawn when the session is unknown', async () => { + await sut.onSegmentRequest({ sessionId: 'never-created', assetId, variantIndex: 0, segmentIndex: 0 }); + + expect(mocks.process.spawn).not.toHaveBeenCalled(); + }); + + it('accepts segments from a restart after the previous ffmpeg exited on its own', async () => { + const first = mockSpawn(0, '', ''); + const second = mockSpawn(0, '', ''); + mocks.process.spawn.mockReturnValueOnce(first).mockReturnValueOnce(second); + + await sut.onSegmentRequest({ sessionId, assetId, variantIndex: 0, segmentIndex: 10 }); + completeSegment(10); + + const onCalls = vi.mocked(first.on).mock.calls as unknown as [string, (code: number) => void][]; + const exitHandler = onCalls.find(([event]) => event === 'exit')?.[1]; + exitHandler?.(0); + + mocks.websocket.serverSend.mockClear(); + await sut.onSegmentRequest({ sessionId, assetId, variantIndex: 0, segmentIndex: 2 }); + completeSegment(2); + + expect(mocks.websocket.serverSend).toHaveBeenCalledWith('HlsSegmentResult', { + sessionId, + variantIndex: 0, + segmentIndex: 2, + }); + }); + }); + + describe('backpressure', () => { + let proc: ReturnType; + + beforeEach(async () => { + proc = mockSpawn(0, '', ''); + mocks.process.spawn.mockReturnValue(proc); + + await sut.onSessionRequest({ sessionId, assetId, ownerId }); + await sut.onSegmentRequest({ sessionId, assetId, variantIndex: 0, segmentIndex: 0 }); + }); + + it('pauses the transcode once the lead exceeds HLS_BACKPRESSURE_PAUSE_SEGMENTS', async () => { + completeSegmentsThrough(0, HLS_BACKPRESSURE_PAUSE_SEGMENTS + 1); + + await sut.onHeartbeat({ sessionId, segmentIndex: 0 }); + + expect(proc.kill).toHaveBeenCalledWith('SIGSTOP'); + }); + + it('does not pause when the lead equals the pause threshold', async () => { + completeSegmentsThrough(0, HLS_BACKPRESSURE_PAUSE_SEGMENTS); + + await sut.onHeartbeat({ sessionId, segmentIndex: 0 }); + + expect(proc.kill).not.toHaveBeenCalled(); + }); + + it('resumes once the lead drops below HLS_BACKPRESSURE_RESUME_SEGMENTS', async () => { + completeSegmentsThrough(0, HLS_BACKPRESSURE_PAUSE_SEGMENTS + 1); + await sut.onHeartbeat({ sessionId, segmentIndex: 0 }); + expect(proc.kill).toHaveBeenCalledWith('SIGSTOP'); + vi.mocked(proc.kill).mockClear(); + + const requested = HLS_BACKPRESSURE_PAUSE_SEGMENTS + 1 - (HLS_BACKPRESSURE_RESUME_SEGMENTS - 1); + await sut.onHeartbeat({ sessionId, segmentIndex: requested }); + + expect(proc.kill).toHaveBeenCalledWith('SIGCONT'); + }); + + it('stays paused while the lead is in the dead-band', async () => { + completeSegmentsThrough(0, HLS_BACKPRESSURE_PAUSE_SEGMENTS + 1); + await sut.onHeartbeat({ sessionId, segmentIndex: 0 }); + vi.mocked(proc.kill).mockClear(); + + const requested = HLS_BACKPRESSURE_PAUSE_SEGMENTS + 1 - HLS_BACKPRESSURE_RESUME_SEGMENTS; + await sut.onHeartbeat({ sessionId, segmentIndex: requested }); + + expect(proc.kill).not.toHaveBeenCalled(); + }); + + it('is a no-op when no segment has completed yet', async () => { + await sut.onHeartbeat({ sessionId, segmentIndex: 0 }); + + expect(proc.kill).not.toHaveBeenCalled(); + }); + + it('is a no-op when the heartbeat omits segmentIndex', async () => { + completeSegmentsThrough(0, HLS_BACKPRESSURE_PAUSE_SEGMENTS + 1); + + await sut.onHeartbeat({ sessionId }); + + expect(proc.kill).not.toHaveBeenCalled(); + }); + + it('resumes the paused transcode when the client requests the next in-range segment', async () => { + completeSegmentsThrough(0, HLS_BACKPRESSURE_PAUSE_SEGMENTS + 1); + await sut.onHeartbeat({ sessionId, segmentIndex: 0 }); + expect(proc.kill).toHaveBeenCalledWith('SIGSTOP'); + vi.mocked(proc.kill).mockClear(); + + await sut.onSegmentRequest({ sessionId, assetId, variantIndex: 0, segmentIndex: 1 }); + + expect(proc.kill).toHaveBeenCalledWith('SIGCONT'); + expect(mocks.process.spawn).toHaveBeenCalledTimes(1); + }); + + it('does not re-pause a freshly spawned transcode after a seek-driven restart', async () => { + const newProc = mockSpawn(0, '', ''); + mocks.process.spawn.mockReturnValueOnce(newProc); + + completeSegmentsThrough(0, HLS_BACKPRESSURE_PAUSE_SEGMENTS + 1); + await sut.onHeartbeat({ sessionId, segmentIndex: 0 }); + expect(proc.kill).toHaveBeenCalledWith('SIGSTOP'); + + await sut.onSegmentRequest({ sessionId, assetId, variantIndex: 1, segmentIndex: 0 }); + vi.mocked(newProc.kill).mockClear(); + + await sut.onHeartbeat({ sessionId, segmentIndex: 0 }); + + expect(newProc.kill).not.toHaveBeenCalled(); + }); + + it('ignores stale segment events from the prior transcode after a backward seek', async () => { + const newProc = mockSpawn(0, '', ''); + mocks.process.spawn.mockReturnValueOnce(newProc); + + const completedAhead = HLS_BACKPRESSURE_PAUSE_SEGMENTS + 5; + completeSegmentsThrough(1, completedAhead); // seg_0 was emitted in beforeEach + + await sut.onSegmentRequest({ sessionId, assetId, variantIndex: 1, segmentIndex: 0 }); + + vi.mocked(newProc.kill).mockClear(); + mocks.websocket.serverSend.mockClear(); + completeSegment(completedAhead + 1); + + expect(mocks.websocket.serverSend).not.toHaveBeenCalledWith( + 'HlsSegmentResult', + expect.objectContaining({ segmentIndex: completedAhead + 1 }), + ); + expect(newProc.kill).not.toHaveBeenCalled(); + + completeSegment(0); + expect(mocks.websocket.serverSend).toHaveBeenCalledWith( + 'HlsSegmentResult', + expect.objectContaining({ segmentIndex: 0 }), + ); + }); + }); + + describe('inactivity sweeper', () => { + it('reaps a session whose last activity exceeds the inactivity timeout', async () => { + vi.useFakeTimers(); + try { + await sut.onSessionRequest({ sessionId, assetId, ownerId }); + mocks.websocket.serverSend.mockClear(); + await vi.advanceTimersByTimeAsync(HLS_INACTIVITY_TIMEOUT_MS + HLS_CLEANUP_INTERVAL_MS); + + expect(mocks.websocket.serverSend).toHaveBeenCalledWith('HlsSessionEnd', { sessionId }); + expect(mocks.videoStream.deleteSession).toHaveBeenCalledWith(sessionId); + } finally { + vi.useRealTimers(); + } + }); + }); + + describe('onShutdown', () => { + it('ends every active session', async () => { + await sut.onSessionRequest({ sessionId: 'session-a', assetId, ownerId }); + await sut.onSessionRequest({ sessionId: 'session-b', assetId, ownerId }); + + await sut.onShutdown(); + + expect(mocks.videoStream.deleteSession).toHaveBeenCalledWith('session-a'); + expect(mocks.videoStream.deleteSession).toHaveBeenCalledWith('session-b'); + }); + }); + + describe('onHlsSessionCleanup', () => { + it('reaps DB-expired sessions under a database lock', async () => { + mocks.database.withLock.mockImplementation(async (_, fn) => fn()); + mocks.videoStream.getExpiredSessions.mockResolvedValue([ + { id: 'expired-1', ownerId: 'user-a' }, + { id: 'expired-2', ownerId: 'user-b' }, + ]); + + await sut.onHlsSessionCleanup(); + + expect(mocks.videoStream.deleteSession).toHaveBeenCalledWith('expired-1'); + expect(mocks.videoStream.deleteSession).toHaveBeenCalledWith('expired-2'); + expect(mocks.storage.unlinkDir).toHaveBeenCalledTimes(2); + }); + }); + + describe('FFmpeg full command', () => { + const baseCommand = [ + '-nostdin', + '-nostats', + '-i', + 'eiffel-tower.mp4', + '-map', + '0:0', + '-map_metadata', + '-1', + '-map', + '0:1', + '-g', + '50', + '-keyint_min', + '50', + '-crf', + '23', + '-copyts', + '-r', + '50130000/2012441', + '-avoid_negative_ts', + 'disabled', + '-f', + 'hls', + '-hls_time', + '2', + '-hls_list_size', + '0', + '-hls_segment_type', + 'fmp4', + '-hls_fmp4_init_filename', + 'init.mp4', + '-hls_segment_options', + 'movflags=+frag_discont', + '-hls_flags', + 'temp_file', + '-start_number', + '0', + ]; + + it.each([ + { + variantIndex: 6, + expected: [ + ...baseCommand, + '-c:v', + 'libsvtav1', + '-c:a', + 'aac', + '-preset', + '12', + '-svtav1-params', + 'hierarchical-levels=3:lookahead=0:enable-tf=0:mbr=4000k', + '-hls_segment_filename', + '/data/encoded-video/user-1/se/ss/session-1/6/seg_%d.m4s', + '/data/encoded-video/user-1/se/ss/session-1/6/playlist.m3u8', + ].sort(), + }, + { + variantIndex: 4, + expected: [ + ...baseCommand, + '-c:v', + 'hevc', + '-c:a', + 'aac', + '-tag:v', + 'hvc1', + '-preset', + 'ultrafast', + '-maxrate', + '2500k', + '-bufsize', + '5000k', + '-x265-params', + 'no-scenecut=1:no-open-gop=1', + '-vf', + 'scale=720:-2', + '-hls_segment_filename', + '/data/encoded-video/user-1/se/ss/session-1/4/seg_%d.m4s', + '/data/encoded-video/user-1/se/ss/session-1/4/playlist.m3u8', + ].sort(), + }, + { + variantIndex: 2, + expected: [ + ...baseCommand, + '-c:v', + 'h264', + '-c:a', + 'aac', + '-preset', + 'ultrafast', + '-maxrate', + '2500k', + '-bufsize', + '5000k', + '-sc_threshold:v', + '0', + '-vf', + 'scale=480:-2', + '-hls_segment_filename', + '/data/encoded-video/user-1/se/ss/session-1/2/seg_%d.m4s', + '/data/encoded-video/user-1/se/ss/session-1/2/playlist.m3u8', + ].sort(), + }, + ])('builds the expected FFmpeg command for $codec (variant $variantIndex)', async ({ variantIndex, expected }) => { + mocks.process.spawn.mockReturnValue(mockSpawn(0, '', '')); + + await sut.onSessionRequest({ sessionId, assetId, ownerId }); + await sut.onSegmentRequest({ sessionId, assetId, variantIndex, segmentIndex: 0 }); + + expect(mocks.process.spawn.mock.calls[0][1].toSorted()).toEqual(expected); + }); + }); + + describe('FFmpeg seek per segment', () => { + const eiffelSeeks = [ + 0, 1.987_15, 3.994_372_222_222_222, 6.001_594_444_444_444, 8.008_816_666_666_666, 10.016_038_888_888_888, + 12.023_261_111_111_111, 14.030_483_333_333_333, 16.037_705_555_555_554, 18.044_927_777_777_776, + 20.052_149_999_999_997, 22.059_372_222_222_223, + ]; + const waterfallSeeks = [ + 0, 1.994_642_826_321_467, 4.006_047_357_065_803, 6.017_451_887_810_139_5, 8.028_856_418_554_476, + 10.040_260_949_298_812, + ]; + const trainSeeks = [ + 0, 1.991_666_666_666_666_7, 3.991_666_666_666_666_7, 5.991_666_666_666_666, 7.991_666_666_666_666, + 9.991_666_666_666_667, 11.991_666_666_666_667, 13.991_666_666_666_667, 15.991_666_666_666_667, + 17.991_666_666_666_667, 19.991_666_666_666_667, + ]; + const cases = [ + ...eiffelSeeks.map((expected, segmentIndex) => ({ + name: `${eiffelTower.originalPath} K=${segmentIndex}`, + fixture: eiffelTower, + segmentIndex, + expected, + })), + ...waterfallSeeks.map((expected, segmentIndex) => ({ + name: `${waterfall.originalPath} K=${segmentIndex}`, + fixture: waterfall, + segmentIndex, + expected, + })), + ...trainSeeks.map((expected, segmentIndex) => ({ + name: `${train.originalPath} K=${segmentIndex}`, + fixture: train, + segmentIndex, + expected, + })), + ]; + + it.each(cases)('$name', async ({ fixture, segmentIndex, expected }) => { + mocks.videoStream.getForTranscoding.mockResolvedValue(fixture); + mocks.process.spawn.mockReturnValue(mockSpawn(0, '', '')); + + await sut.onSessionRequest({ sessionId, assetId, ownerId }); + await sut.onSegmentRequest({ sessionId, assetId, variantIndex: 0, segmentIndex }); + + const args = mocks.process.spawn.mock.calls[0][1] as string[]; + if (expected === 0) { + expect(args).toEqual(expect.arrayContaining(['-copyts', '-avoid_negative_ts', 'disabled'])); + expect(args).not.toContain('-ss'); + } else { + expect(args).toEqual( + expect.arrayContaining(['-ss', String(expected), '-copyts', '-avoid_negative_ts', 'disabled']), + ); + } + }); + }); +}); diff --git a/server/src/services/transcoding.service.ts b/server/src/services/transcoding.service.ts new file mode 100644 index 0000000000..69e2529d63 --- /dev/null +++ b/server/src/services/transcoding.service.ts @@ -0,0 +1,387 @@ +import { Injectable } from '@nestjs/common'; +import { ChildProcess } from 'node:child_process'; +import { join } from 'node:path'; +import { + HLS_BACKPRESSURE_PAUSE_SEGMENTS, + HLS_BACKPRESSURE_RESUME_SEGMENTS, + HLS_CLEANUP_INTERVAL_MS, + HLS_INACTIVITY_TIMEOUT_MS, + HLS_LEASE_DURATION_MS, + HLS_SEGMENT_DURATION, + HLS_SEGMENT_FILENAME_REGEX, + HLS_VARIANTS, +} from 'src/constants'; +import { StorageCore } from 'src/cores/storage.core'; +import { OnEvent, OnJob } from 'src/decorators'; +import { DatabaseLock, ImmichWorker, JobName, QueueName, TranscodeTarget } from 'src/enum'; +import { ArgOf } from 'src/repositories/event.repository'; +import { BaseService } from 'src/services/base.service'; +import { VideoInterfaces } from 'src/types'; +import { isVideoStreamSessionPkConstraint } from 'src/utils/database'; +import { BaseConfig } from 'src/utils/media'; + +type Session = { + assetId: string; + expiresAt: Date; + id: string; + lastActivityTime: Date; + lastClientRequestedSegment: number | null; + lastCompletedSegment: number | null; + ownerId: string; + paused: boolean; + process: ChildProcess | null; + startSegment: number | null; + variantIndex: number | null; +}; + +@Injectable() +export class TranscodingService extends BaseService { + private sessions = new Map(); + private videoInterfaces: VideoInterfaces = { dri: [], mali: false }; + private cleanupInterval: NodeJS.Timeout | null = null; + + @OnEvent({ name: 'AppBootstrap', workers: [ImmichWorker.Microservices] }) + async onBootstrap() { + const [videoInterfaces] = await Promise.all([this.storageCore.getVideoInterfaces(), this.removeExpiredSessions()]); + this.videoInterfaces = videoInterfaces; + } + + @OnEvent({ name: 'AppShutdown', workers: [ImmichWorker.Microservices] }) + onShutdown() { + if (this.cleanupInterval) { + clearInterval(this.cleanupInterval); + this.cleanupInterval = null; + } + return Promise.all([...this.sessions.values()].map(({ id }) => this.onSessionEnd({ sessionId: id }))); + } + + @OnJob({ name: JobName.HlsSessionCleanup, queue: QueueName.BackgroundTask }) + onHlsSessionCleanup() { + return this.removeExpiredSessions(); + } + + @OnEvent({ name: 'HlsSessionRequest', server: true, workers: [ImmichWorker.Microservices] }) + async onSessionRequest({ assetId, sessionId, ownerId }: ArgOf<'HlsSessionRequest'>) { + try { + const expiresAt = new Date(Date.now() + HLS_LEASE_DURATION_MS); + await this.videoStreamRepository.createSession({ id: sessionId, assetId, expiresAt }); + this.sessions.set(sessionId, { + assetId, + expiresAt, + id: sessionId, + lastActivityTime: new Date(), + lastClientRequestedSegment: null, + lastCompletedSegment: null, + ownerId, + paused: false, + process: null, + startSegment: null, + variantIndex: null, + }); + this.cleanupInterval ??= setInterval(() => void this.removeInactiveSessions(), HLS_CLEANUP_INTERVAL_MS); + this.websocketRepository.serverSend('HlsSessionResult', { sessionId }); + } catch (error) { + // If insertion failed due to a PK constraint, another worker has already created a session for this ID. + if (!isVideoStreamSessionPkConstraint(error)) { + this.logger.error(`Failed to create HLS session ${sessionId}: ${error}`); + this.websocketRepository.serverSend('HlsSessionResult', { sessionId, error: 'Failed to create HLS session' }); + } + } + } + + @OnEvent({ name: 'HlsSessionEnd', server: true, workers: [ImmichWorker.Microservices] }) + async onSessionEnd({ sessionId }: ArgOf<'HlsSessionEnd'>) { + const session = this.sessions.get(sessionId); + if (!session) { + return; + } + this.sessions.delete(sessionId); + if (this.cleanupInterval && this.sessions.size === 0) { + clearInterval(this.cleanupInterval); + this.cleanupInterval = null; + } + this.stopTranscode(session); + await this.removeSessionDir(session); + await this.videoStreamRepository.deleteSession(sessionId); + } + + @OnEvent({ name: 'HlsHeartbeat', server: true, workers: [ImmichWorker.Microservices] }) + async onHeartbeat({ sessionId, segmentIndex }: ArgOf<'HlsHeartbeat'>) { + const session = this.sessions.get(sessionId); + if (!session) { + return; + } + + session.lastActivityTime = new Date(); + + if (segmentIndex !== undefined) { + session.lastClientRequestedSegment = segmentIndex; + this.applyBackpressure(session); + } + + const remaining = session.expiresAt.getTime() - Date.now(); + if (remaining < HLS_LEASE_DURATION_MS / 2) { + session.expiresAt = new Date(Date.now() + HLS_LEASE_DURATION_MS); + await this.videoStreamRepository.extendSession(sessionId, session.expiresAt); + } + } + + @OnEvent({ name: 'HlsSegmentRequest', server: true, workers: [ImmichWorker.Microservices] }) + async onSegmentRequest({ sessionId, variantIndex, segmentIndex }: ArgOf<'HlsSegmentRequest'>) { + const session = this.sessions.get(sessionId); + if (!session) { + return; + } + + session.variantIndex ??= variantIndex; + session.startSegment ??= segmentIndex; + const curSegment = session.lastCompletedSegment === null ? session.startSegment : session.lastCompletedSegment + 1; + const needsRestart = + session.variantIndex !== variantIndex || segmentIndex < session.startSegment || segmentIndex > curSegment + 1; + if (needsRestart) { + this.stopTranscode(session); + session.variantIndex = variantIndex; + session.startSegment = segmentIndex; + } else if (session.process) { + this.resumeTranscode(session); + return; + } + + const process = await this.startTranscode(session, variantIndex, segmentIndex); + if (process) { + session.process = process; + } + } + + private applyBackpressure(session: Session) { + if (session.lastCompletedSegment === null || session.lastClientRequestedSegment === null) { + return; + } + const lead = session.lastCompletedSegment - session.lastClientRequestedSegment; + this.logger.debug(`Session ${session.id} lead is ${lead} segments`); + if (!session.paused && lead > HLS_BACKPRESSURE_PAUSE_SEGMENTS) { + this.pauseTranscode(session); + } else if (session.paused && lead < HLS_BACKPRESSURE_RESUME_SEGMENTS) { + this.resumeTranscode(session); + } + } + + private async startTranscode(session: Session, variantIndex: number, startSegment: number) { + const { ffmpeg } = await this.getConfig({ withCache: true }); + + const asset = await this.videoStreamRepository.getForTranscoding(session.assetId); + if (!asset) { + this.logger.error(`Asset ${session.assetId} not found for HLS transcoding`); + return; + } + + if (session.variantIndex !== variantIndex || session.startSegment !== startSegment) { + return; + } + + const variant = HLS_VARIANTS[variantIndex]; + if (!variant) { + this.logger.error(`Variant ${variantIndex} out of range for asset ${session.assetId}`); + await this.failSession(session, `Invalid variant index ${variantIndex}`); + return; + } + + const variantDir = StorageCore.getHlsVariantFolder({ + ownerId: session.ownerId, + sessionId: session.id, + variantIndex, + }); + this.storageRepository.mkdirSync(variantDir); + + // Encoder runs at fps = packetCount ร— timeBase / totalDuration with + // gop = ceil(SEGMENT_DURATION ร— fps). To start segment K's content at + // exactly cfr slot K ร— gop, seek to the midpoint between slots Kร—gopโˆ’1 and + // Kร—gop. accurate_seek's "discard < target" then keeps the source frame + // that quantizes to slot Kร—gop and discards the one quantizing to Kร—gopโˆ’1. + const fps = (asset.packets.packetCount * asset.videoStream.timeBase) / asset.packets.totalDuration; + const gop = Math.ceil(HLS_SEGMENT_DURATION * fps); + const seekSeconds = startSegment > 0 ? (startSegment * gop - 0.5) / fps : 0; + + let config; + try { + config = BaseConfig.create( + { + ...ffmpeg, + targetVideoCodec: variant.codec, + targetResolution: String(variant.resolution), + maxBitrate: `${Math.round(variant.bitrate / 1000)}k`, + gopSize: gop, + }, + this.videoInterfaces, + { strictGop: true, lowLatency: true }, + ); + } catch (error: any) { + this.logger.error( + `Failed to create transcode config for variant ${variantIndex} asset ${session.assetId}: ${error?.message ?? error}`, + ); + await this.failSession(session, `Failed to start transcode: ${error?.message ?? 'unknown error'}`); + return; + } + const args = config.getHlsCommand( + { + initFilename: 'init.mp4', + inputPath: asset.originalPath, + packetCount: asset.packets.packetCount, + playlistFilename: join(variantDir, 'playlist.m3u8'), + seekSeconds, + segmentDuration: HLS_SEGMENT_DURATION, + segmentFilename: join(variantDir, 'seg_%d.m4s'), + startSegment, + target: TranscodeTarget.All, + timeBase: asset.videoStream.timeBase, + totalDuration: asset.packets.totalDuration, + }, + asset.videoStream, + asset.audioStream ?? undefined, + ); + this.logger.log( + `Starting HLS transcode for asset ${session.assetId} variant ${variantIndex} with command: ffmpeg ${args.join(' ')}`, + ); + const process = this.processRepository.spawn('ffmpeg', args, { stdio: ['ignore', 'ignore', 'pipe'] }); + this.attachProcessHandlers(process, session, variantIndex); + return process; + } + + private failSession(session: Session, error: string) { + this.websocketRepository.serverSend('HlsSessionResult', { sessionId: session.id, error }); + return this.onSessionEnd({ sessionId: session.id }); + } + + private attachProcessHandlers(process: ChildProcess, session: Session, variantIndex: number) { + let stderr = ''; + const variantDir = StorageCore.getHlsVariantFolder({ + ownerId: session.ownerId, + sessionId: session.id, + variantIndex, + }); + + // hlsenc writes each segment as `seg_K.m4s.tmp` then renames to + // `seg_K.m4s`. The rename event fires the moment the renamed file is + // observable โ€” the only signal we need to tell the API worker the + // segment is ready to serve. + const watcher = this.storageRepository.watchDir(variantDir, (eventType, filename) => { + if (eventType !== 'rename' || !filename || session.process !== process) { + return; + } + const match = HLS_SEGMENT_FILENAME_REGEX.exec(filename); + if (!match) { + return; + } + const segmentIndex = Number.parseInt(match[1]); + const expected = session.lastCompletedSegment === null ? session.startSegment : session.lastCompletedSegment + 1; + // Ignore stale events from old process after seek + if (expected === null || segmentIndex !== expected) { + return; + } + session.lastCompletedSegment = segmentIndex; + this.websocketRepository.serverSend('HlsSegmentResult', { + sessionId: session.id, + variantIndex, + segmentIndex, + }); + this.applyBackpressure(session); + }); + watcher.on('error', (error) => { + this.logger.error(`watcher error for ${variantDir}: ${error}`); + }); + + process.stderr!.on('data', (chunk: Buffer) => { + if (session.process !== process) { + return; + } + stderr += chunk.toString(); + }); + + process.on('exit', (code) => { + watcher.close(); + if (session.process !== process || session.variantIndex !== variantIndex) { + return; + } + session.paused = false; + session.process = null; + session.lastCompletedSegment = null; + if (code) { + this.logger.error( + `FFmpeg exited with code ${code} for variant ${variantIndex} asset ${session.assetId}\n${stderr}`, + ); + void this.failSession(session, `Transcoding process exited unexpectedly with code ${code}`).catch((error) => + this.logger.error(`Failed to end session ${session.id} after ffmpeg exit: ${error}`), + ); + } + }); + } + + private stopTranscode(session: Session) { + if (!session.process) { + return; + } + // SIGTERM makes it rename .tmp segments to .m4s even if they're still incomplete + session.process.kill('SIGKILL'); + session.process = null; + session.lastCompletedSegment = null; + session.paused = false; + this.logger.debug(`Stopped transcoding for session ${session.id}`); + } + + private pauseTranscode(session: Session) { + if (session.paused || !session.process) { + return; + } + session.process.kill('SIGSTOP'); + session.paused = true; + this.logger.debug(`Paused transcoding for session ${session.id}`); + } + + private resumeTranscode(session: Session) { + if (!session.paused || !session.process) { + return; + } + session.process.kill('SIGCONT'); + session.paused = false; + this.logger.debug(`Resumed transcoding for session ${session.id}`); + } + + private async removeSessionDir(session: { ownerId: string; id: string }) { + const dir = StorageCore.getHlsSessionFolder({ ownerId: session.ownerId, sessionId: session.id }); + try { + await this.storageRepository.unlinkDir(dir, { recursive: true, force: true }); + } catch (error) { + if ((error as NodeJS.ErrnoException)?.code !== 'ENOENT') { + throw error; + } + this.logger.warn(`Session dir ${dir} does not exist.`); + } + } + + private removeInactiveSessions() { + const cutoff = Date.now() - HLS_INACTIVITY_TIMEOUT_MS; + const inactiveSessions = [...this.sessions.values()].filter((s) => s.lastActivityTime.getTime() < cutoff); + return Promise.all( + inactiveSessions.map(async (session) => { + try { + this.websocketRepository.serverSend('HlsSessionEnd', { sessionId: session.id }); + await this.onSessionEnd({ sessionId: session.id }); + } catch (error) { + this.logger.error(`Failed to sweep inactive HLS session ${session.id}: ${error}`); + } + }), + ); + } + + private removeExpiredSessions() { + return this.databaseRepository.withLock(DatabaseLock.HlsSessionCleanup, async () => { + const expiredSessions = await this.videoStreamRepository.getExpiredSessions(); + await Promise.all( + expiredSessions.map(async (session) => { + await this.removeSessionDir(session); + await this.videoStreamRepository.deleteSession(session.id); + }), + ); + }); + } +} diff --git a/server/src/services/user.service.ts b/server/src/services/user.service.ts index 82ab90a590..27084eb3b4 100644 --- a/server/src/services/user.service.ts +++ b/server/src/services/user.service.ts @@ -3,7 +3,7 @@ import { Updateable } from 'kysely'; import { DateTime } from 'luxon'; import { SALT_ROUNDS } from 'src/constants'; import { StorageCore } from 'src/cores/storage.core'; -import { OnJob } from 'src/decorators'; +import { OnEvent, OnJob } from 'src/decorators'; import { AuthDto } from 'src/dtos/auth.dto'; import { LicenseKeyDto, LicenseResponseDto } from 'src/dtos/license.dto'; import { OnboardingDto, OnboardingResponseDto } from 'src/dtos/onboarding.dto'; @@ -11,6 +11,7 @@ import { UserPreferencesResponseDto, UserPreferencesUpdateDto, mapPreferences } import { CreateProfileImageResponseDto } from 'src/dtos/user-profile.dto'; import { UserAdminResponseDto, UserResponseDto, UserUpdateMeDto, mapUser, mapUserAdmin } from 'src/dtos/user.dto'; import { CacheControl, JobName, JobStatus, QueueName, StorageFolder, UserMetadataKey } from 'src/enum'; +import { ArgOf } from 'src/repositories/event.repository'; import { UserFindOptions } from 'src/repositories/user.repository'; import { UserTable } from 'src/schema/tables/user.table'; import { BaseService } from 'src/services/base.service'; @@ -230,6 +231,11 @@ export class UserService extends BaseService { }; } + @OnEvent({ name: 'AssetCreate' }) + async onAssetCreate({ asset, file }: ArgOf<'AssetCreate'>) { + await this.userRepository.updateUsage(asset.ownerId, file.size); + } + @OnJob({ name: JobName.UserSyncUsage, queue: QueueName.BackgroundTask }) async handleUserSyncUsage(): Promise { await this.userRepository.syncUsage(); diff --git a/server/src/services/version.service.spec.ts b/server/src/services/version.service.spec.ts index 2fbe7292fa..d73edb9850 100644 --- a/server/src/services/version.service.spec.ts +++ b/server/src/services/version.service.spec.ts @@ -2,6 +2,7 @@ import { DateTime } from 'luxon'; import { SemVer } from 'semver'; import { defaults } from 'src/config'; import { serverVersion } from 'src/constants'; +import { ReleaseChannel } from 'src/dtos/system-config.dto'; import { CronJob, JobName, JobStatus, SystemMetadataKey } from 'src/enum'; import { VersionService } from 'src/services/version.service'; import { factory } from 'test/small.factory'; @@ -22,6 +23,17 @@ describe(VersionService.name, () => { mocks.cron.update.mockResolvedValue(); }); + beforeAll(() => { + vitest.mock(import('src/constants.js'), async () => ({ + ...(await vitest.importActual('src/constants.js')), + serverVersion: new SemVer('v3.0.0'), + })); + }); + + afterAll(() => { + vitest.unmock(import('src/constants.js')); + }); + it('should work', () => { expect(sut).toBeDefined(); }); @@ -66,9 +78,10 @@ describe(VersionService.name, () => { describe('getVersion', () => { it('should respond the server version', () => { expect(sut.getVersion()).toEqual({ - major: serverVersion.major, - minor: serverVersion.minor, - patch: serverVersion.patch, + major: 3, + minor: 0, + patch: 0, + prerelease: null, }); }); }); @@ -143,24 +156,24 @@ describe(VersionService.name, () => { describe('onConfigUpdate', () => { it('should queue a version check job when newVersionCheck is enabled', async () => { await sut.onConfigUpdate({ - oldConfig: { ...defaults, newVersionCheck: { enabled: false } }, - newConfig: { ...defaults, newVersionCheck: { enabled: true } }, + oldConfig: { ...defaults, newVersionCheck: { enabled: false, channel: ReleaseChannel.Stable } }, + newConfig: { ...defaults, newVersionCheck: { enabled: true, channel: ReleaseChannel.Stable } }, }); expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.VersionCheck, data: {} }); }); it('should not queue a version check job when newVersionCheck is disabled', async () => { await sut.onConfigUpdate({ - oldConfig: { ...defaults, newVersionCheck: { enabled: true } }, - newConfig: { ...defaults, newVersionCheck: { enabled: false } }, + oldConfig: { ...defaults, newVersionCheck: { enabled: true, channel: ReleaseChannel.Stable } }, + newConfig: { ...defaults, newVersionCheck: { enabled: false, channel: ReleaseChannel.Stable } }, }); expect(mocks.job.queue).not.toHaveBeenCalled(); }); it('should not queue a version check job when newVersionCheck was already enabled', async () => { await sut.onConfigUpdate({ - oldConfig: { ...defaults, newVersionCheck: { enabled: true } }, - newConfig: { ...defaults, newVersionCheck: { enabled: true } }, + oldConfig: { ...defaults, newVersionCheck: { enabled: true, channel: ReleaseChannel.Stable } }, + newConfig: { ...defaults, newVersionCheck: { enabled: true, channel: ReleaseChannel.Stable } }, }); expect(mocks.job.queue).not.toHaveBeenCalled(); }); @@ -169,21 +182,36 @@ describe(VersionService.name, () => { describe('onWebsocketConnection', () => { it('should send on_server_version client event', async () => { await sut.onWebsocketConnection({ userId: '42' }); - expect(mocks.websocket.clientSend).toHaveBeenCalledWith('on_server_version', '42', expect.any(SemVer)); + expect(mocks.websocket.clientSend).toHaveBeenCalledWith('on_server_version', '42', { + major: 3, + minor: 0, + patch: 0, + prerelease: null, + }); expect(mocks.websocket.clientSend).toHaveBeenCalledTimes(1); }); it('should also send a new release notification', async () => { mocks.systemMetadata.get.mockResolvedValue({ checkedAt: '2024-01-01', releaseVersion: 'v1.42.0' }); await sut.onWebsocketConnection({ userId: '42' }); - expect(mocks.websocket.clientSend).toHaveBeenCalledWith('on_server_version', '42', expect.any(SemVer)); + expect(mocks.websocket.clientSend).toHaveBeenCalledWith('on_server_version', '42', { + major: 3, + minor: 0, + patch: 0, + prerelease: null, + }); expect(mocks.websocket.clientSend).toHaveBeenCalledWith('on_new_release', '42', expect.any(Object)); }); it('should not send a release notification when the version check is disabled', async () => { mocks.systemMetadata.get.mockResolvedValueOnce({ newVersionCheck: { enabled: false } }); await sut.onWebsocketConnection({ userId: '42' }); - expect(mocks.websocket.clientSend).toHaveBeenCalledWith('on_server_version', '42', expect.any(SemVer)); + expect(mocks.websocket.clientSend).toHaveBeenCalledWith('on_server_version', '42', { + major: 3, + minor: 0, + patch: 0, + prerelease: null, + }); expect(mocks.websocket.clientSend).not.toHaveBeenCalledWith('on_new_release', '42', expect.any(Object)); }); }); diff --git a/server/src/services/version.service.ts b/server/src/services/version.service.ts index ce6d6d7a6f..37010db5e7 100644 --- a/server/src/services/version.service.ts +++ b/server/src/services/version.service.ts @@ -3,19 +3,27 @@ import { DateTime } from 'luxon'; import semver, { SemVer } from 'semver'; import { serverVersion } from 'src/constants'; import { OnEvent, OnJob } from 'src/decorators'; -import { ReleaseNotification, ServerVersionResponseDto } from 'src/dtos/server.dto'; +import { ReleaseEventV1, ReleaseType, ServerVersionResponseDto } from 'src/dtos/server.dto'; +import { ReleaseChannel } from 'src/dtos/system-config.dto'; import { CronJob, DatabaseLock, ImmichWorker, JobName, JobStatus, QueueName, SystemMetadataKey } from 'src/enum'; import { ArgOf } from 'src/repositories/event.repository'; import { BaseService } from 'src/services/base.service'; import { VersionCheckMetadata } from 'src/types'; import { handlePromiseError } from 'src/utils/misc'; -const asNotification = ({ checkedAt, releaseVersion }: VersionCheckMetadata): ReleaseNotification => { +const asNotification = ( + channel: ReleaseChannel, + { checkedAt, releaseVersion }: VersionCheckMetadata, +): ReleaseEventV1 => { return { - isAvailable: semver.gt(releaseVersion, serverVersion), + // can't use gt because it's broken for release candidates F https://github.com/npm/node-semver/issues/483 + isAvailable: semver.intersects(`>${serverVersion}`, releaseVersion.toString(), { + includePrerelease: channel === ReleaseChannel.ReleaseCandidate, + }), checkedAt, serverVersion: ServerVersionResponseDto.fromSemVer(serverVersion), releaseVersion: ServerVersionResponseDto.fromSemVer(new SemVer(releaseVersion)), + type: semver.diff(serverVersion, releaseVersion) as ReleaseType, }; }; @@ -98,14 +106,21 @@ export class VersionService extends BaseService { } } - const { version: releaseVersion, published_at: publishedAt } = await this.serverInfoRepository.getLatestRelease(); + const { version: releaseVersion, published_at: publishedAt } = await this.serverInfoRepository.getLatestRelease( + newVersionCheck.channel, + ); const metadata: VersionCheckMetadata = { checkedAt: DateTime.utc().toISO(), releaseVersion }; await this.systemMetadataRepository.set(SystemMetadataKey.VersionCheckState, metadata); - if (semver.gt(releaseVersion, serverVersion)) { + // can't use gt because it's broken for release candidates F https://github.com/npm/node-semver/issues/483 + if ( + semver.intersects(`>${serverVersion}`, releaseVersion.toString(), { + includePrerelease: newVersionCheck.channel === ReleaseChannel.ReleaseCandidate, + }) + ) { this.logger.log(`Found ${releaseVersion}, released at ${new Date(publishedAt).toLocaleString()}`); - this.websocketRepository.clientBroadcast('on_new_release', asNotification(metadata)); + this.websocketRepository.clientBroadcast('on_new_release', asNotification(newVersionCheck.channel, metadata)); } } catch (error: Error | any) { this.logger.warn(`Unable to run version check: ${error}\n${error?.stack}`); @@ -117,7 +132,11 @@ export class VersionService extends BaseService { @OnEvent({ name: 'WebsocketConnect' }) async onWebsocketConnection({ userId }: ArgOf<'WebsocketConnect'>) { - this.websocketRepository.clientSend('on_server_version', userId, serverVersion); + this.websocketRepository.clientSend( + 'on_server_version', + userId, + ServerVersionResponseDto.fromSemVer(serverVersion), + ); const { newVersionCheck } = await this.getConfig({ withCache: true }); if (!newVersionCheck.enabled) { @@ -126,7 +145,7 @@ export class VersionService extends BaseService { const metadata = await this.systemMetadataRepository.get(SystemMetadataKey.VersionCheckState); if (metadata) { - this.websocketRepository.clientSend('on_new_release', userId, asNotification(metadata)); + this.websocketRepository.clientSend('on_new_release', userId, asNotification(newVersionCheck.channel, metadata)); } } } diff --git a/server/src/services/workflow-execution.service.ts b/server/src/services/workflow-execution.service.ts new file mode 100644 index 0000000000..0a5f025fc1 --- /dev/null +++ b/server/src/services/workflow-execution.service.ts @@ -0,0 +1,427 @@ +import { CurrentPlugin } from '@extism/extism'; +import { + WorkflowChanges, + WorkflowEventData, + WorkflowEventPayload, + WorkflowResponse, + WorkflowTrigger, +} from '@immich/plugin-sdk'; +import { HttpException, UnauthorizedException } from '@nestjs/common'; +import { join } from 'node:path'; +import { DummyValue, OnEvent, OnJob } from 'src/decorators'; +import { AlbumsAddAssetsDto, CreateAlbumDto, GetAlbumsDto } from 'src/dtos/album.dto'; +import { BulkIdsDto } from 'src/dtos/asset-ids.response.dto'; +import { AuthDto } from 'src/dtos/auth.dto'; +import { PluginManifestDto } from 'src/dtos/plugin-manifest.dto'; +import { + BootstrapEventPriority, + DatabaseLock, + ImmichEnvironment, + ImmichWorker, + JobName, + JobStatus, + QueueName, + WorkflowType, +} from 'src/enum'; +import { ArgOf } from 'src/repositories/event.repository'; +import { AlbumService } from 'src/services/album.service'; +import { AssetService } from 'src/services/asset.service'; +import { BaseService } from 'src/services/base.service'; +import { JobOf } from 'src/types'; + +const dummy = () => { + throw new Error( + `Calling host functions is not allowed without setting methods[].hostFunctions=true in the plugin manifest`, + ); +}; + +type ExecuteOptions = { + read: (type: T) => Promise<{ authUserId: string; data: WorkflowEventData }>; + write: (auth: AuthDto, changes: WorkflowChanges) => Promise; +}; + +type AssetTrigger = { userId: string; assetId: string; trigger: WorkflowTrigger }; + +export class WorkflowExecutionService extends BaseService { + private jwtSecret!: string; + + @OnEvent({ name: 'AppBootstrap', priority: BootstrapEventPriority.PluginSync, workers: [ImmichWorker.Microservices] }) + async onPluginSync() { + await this.databaseRepository.withLock(DatabaseLock.PluginImport, async () => { + // TODO avoid importing plugins in each worker + // Can this use system metadata similar to geocoding? + + const { environment, resourcePaths, plugins } = this.configRepository.getEnv(); + await this.importFolder(resourcePaths.corePlugin, { force: environment === ImmichEnvironment.Development }); + + if (plugins.external.allow && plugins.external.installFolder) { + await this.importFolders(plugins.external.installFolder); + } + }); + } + + @OnEvent({ name: 'AppBootstrap', priority: BootstrapEventPriority.PluginLoad, workers: [ImmichWorker.Microservices] }) + async onPluginLoad() { + this.jwtSecret = this.cryptoRepository.randomBytesAsText(32); + + const albumService = BaseService.create(AlbumService, this); + + const searchAlbums = this.wrap<[dto: GetAlbumsDto]>((authDto, args) => albumService.getAll(authDto, ...args)); + const createAlbum = this.wrap<[dto: CreateAlbumDto]>((authDto, args) => albumService.create(authDto, ...args)); + const addAssetsToAlbum = this.wrap<[id: string, dto: BulkIdsDto]>((authDto, args) => + albumService.addAssets(authDto, ...args), + ); + const addAssetsToAlbums = this.wrap<[dto: AlbumsAddAssetsDto]>((authDto, args) => + albumService.addAssetsToAlbums(authDto, ...args), + ); + + const functions = { + searchAlbums, + createAlbum, + addAssetsToAlbum, + addAssetsToAlbums, + }; + + const stubs: typeof functions = { + searchAlbums: dummy, + createAlbum: dummy, + addAssetsToAlbum: dummy, + addAssetsToAlbums: dummy, + }; + + const plugins = await this.pluginRepository.getForLoad(); + for (const { id, name, version, wasmBytes, methods } of plugins) { + const method = methods.some(({ hostFunctions }) => !hostFunctions); + if (method) { + const label = `${name}@${version}`; + const key = this.getPluginKey({ id, hostFunctions: false }); + try { + await this.pluginRepository.load({ key, label, wasmBytes }, { runInWorker: false, functions: stubs }); + this.logger.log(`Loaded plugin: ${label}`); + } catch (error) { + this.logger.error(`Unable to load plugin ${label} (${id})`, error); + } + } + + const methodWithFunction = methods.some(({ hostFunctions }) => hostFunctions); + if (methodWithFunction) { + const label = `${name}@${version}/worker`; + const key = this.getPluginKey({ id, hostFunctions: true }); + try { + await this.pluginRepository.load({ key, label, wasmBytes }, { runInWorker: true, functions }); + this.logger.log(`Loaded plugin with host functions: ${label}`); + } catch (error) { + this.logger.error(`Unable to load plugin with host functions ${label} (${id})`, error); + } + } + } + } + + private getPluginKey({ id, hostFunctions }: { id: string; hostFunctions: boolean }) { + return id + (hostFunctions ? '/worker' : ''); + } + + private wrap(fn: (authDto: AuthDto, args: T) => Promise) { + return async (plugin: CurrentPlugin, offset: bigint) => { + try { + const handle = plugin.read(offset); + if (!handle) { + return plugin.store( + JSON.stringify({ success: false, status: 400, message: 'Called host function without input' }), + ); + } + + const { authToken, args } = handle.json() as { authToken: string; args: T }; + if (!authToken) { + throw new Error('authToken is required'); + } + + const authDto = this.validate(authToken); + const response = await fn(authDto, args); + + return plugin.store(JSON.stringify({ success: true, response })); + } catch (error: Error | any) { + if (error instanceof HttpException) { + this.logger.error(`Plugin host exception: ${error}`); + return plugin.store( + JSON.stringify({ success: false, status: error.getStatus(), message: error.getResponse() }), + ); + } + + this.logger.error(`Plugin host exception: ${error}`, error?.stack); + + return plugin.store( + JSON.stringify({ + success: false, + status: 500, + message: `Internal server error: ${error}`, + }), + ); + } + }; + } + + private async importFolders(installFolder: string): Promise { + try { + const entries = await this.storageRepository.readdirWithTypes(installFolder); + for (const entry of entries) { + if (!entry.isDirectory()) { + continue; + } + + await this.importFolder(join(installFolder, entry.name)); + } + } catch (error) { + this.logger.error(`Failed to import plugins folder ${installFolder}:`, error); + } + } + + private async importFolder(folder: string, options?: { force?: boolean }) { + try { + const manifestPath = join(folder, 'manifest.json'); + const bytes = await this.storageRepository.readFile(manifestPath); + const contents = bytes.toString('utf8'); + const sha256hash = this.cryptoRepository.hashSha256(contents) as Buffer; + + if (!options?.force) { + const match = await this.pluginRepository.getByHash(sha256hash); + if (match) { + this.logger.log(`Plugin up to date (name=${match.name}@${match.version}, hash=${sha256hash.toString('hex')}`); + return; + } + } + + const dto = JSON.parse(contents); + const result = PluginManifestDto.schema.safeParse(dto); + if (!result.success) { + const issues = result.error.issues.map((issue) => ` - [${issue.path.join('.')}] ${issue.message}`).join('\n'); + this.logger.warn(`Invalid plugin manifest at ${manifestPath}:\n${issues}`); + return; + } + const manifest = result.data; + + const existing = await this.pluginRepository.getByName(manifest.name); + const wasmPath = `${folder}/${manifest.wasmPath}`; + const wasmBytes = await this.storageRepository.readFile(wasmPath); + + const plugin = await this.pluginRepository.upsert( + { + // NOTE: new properties here need to be added to the on conflict clause in the repository + enabled: true, + name: manifest.name, + title: manifest.title, + description: manifest.description, + author: manifest.author, + version: manifest.version, + templates: manifest.templates, + wasmBytes, + sha256hash, + }, + manifest.methods, + ); + + if (existing) { + this.logger.log( + `Upgraded plugin ${manifest.name} (${plugin.methods.length} methods) from ${existing.version} to ${manifest.version} `, + ); + } else { + this.logger.log( + `Imported plugin ${manifest.name}@${manifest.version} (${plugin.methods.length} methods) from ${folder}`, + ); + } + + return manifest; + } catch { + this.logger.warn(`Failed to import plugin from ${folder}:`); + } + } + + private validate(authToken: string): AuthDto { + try { + const jwt = this.cryptoRepository.verifyJwt<{ userId: string }>(authToken, this.jwtSecret); + if (!jwt.userId) { + throw new UnauthorizedException('Invalid token: missing userId'); + } + + return { + user: { + id: jwt.userId, + }, + } as AuthDto; + } catch (error) { + this.logger.error('Token validation failed:', error); + throw new UnauthorizedException('Invalid token'); + } + } + + private sign(userId: string) { + return this.cryptoRepository.signJwt({ userId }, this.jwtSecret); + } + + @OnEvent({ name: 'AssetCreate' }) + onAssetCreate({ asset: { ownerId: userId, id: assetId } }: ArgOf<'AssetCreate'>) { + return this.onAssetTrigger({ userId, assetId, trigger: WorkflowTrigger.AssetCreate }); + } + + @OnEvent({ name: 'AssetMetadataExtracted' }) + onAssetMetadataExtracted({ userId, assetId, source }: ArgOf<'AssetMetadataExtracted'>) { + // prevent loops + // TODO loop detection in job service directly + if (source === 'sidecar-write') { + return; + } + + return this.onAssetTrigger({ userId, assetId, trigger: WorkflowTrigger.AssetMetadataExtraction }); + } + + private async onAssetTrigger({ userId, assetId, trigger }: AssetTrigger) { + const items = await this.workflowRepository.search({ userId, trigger }); + await this.jobRepository.queueAll( + items.map((workflow) => ({ + name: JobName.WorkflowAssetTrigger, + data: { workflowId: workflow.id, assetId, trigger }, + })), + ); + } + + @OnJob({ name: JobName.WorkflowAssetTrigger, queue: QueueName.Workflow }) + handleAssetTrigger({ workflowId, assetId }: JobOf) { + return this.execute(workflowId, (type) => { + const assetService = BaseService.create(AssetService, this); + + switch (type) { + case WorkflowType.AssetV1: { + return { + read: async () => { + const asset = await this.workflowRepository.getForAssetV1(assetId); + return { + data: { asset } as any, + authUserId: asset.ownerId, + }; + }, + write: async (auth, changes) => { + const asset = changes.asset; + if (!asset) { + return; + } + + await assetService.update(auth, assetId, { + isFavorite: asset.isFavorite, + visibility: asset.visibility, + dateTimeOriginal: asset.exifInfo?.dateTimeOriginal ?? undefined, + // TODO allow setting to null + longitude: asset.exifInfo?.longitude ?? undefined, + // TODO allow setting to null + latitude: asset.exifInfo?.latitude ?? undefined, + // TODO allow setting to null + description: asset.exifInfo?.description ?? undefined, + rating: asset.exifInfo?.rating, + + // TODO add to update dto + // make: asset.exifInfo?.make, + // model: asset.exifInfo?.model, + // city: asset.exifInfo?.city, + // state: asset.exifInfo?.state, + // country: asset.exifInfo?.country, + // lensModel: asset.exifInfo?.lensModel, + // fNumber: asset.exifInfo?.fNumber, + // fps: asset.exifInfo?.fps, + // iso: asset.exifInfo?.iso, + }); + }, + } satisfies ExecuteOptions; + } + } + }); + } + + private async execute( + workflowId: string, + getHandler: (type: T) => ExecuteOptions | undefined, + ) { + const workflow = await this.workflowRepository.getForWorkflowRun(workflowId); + if (!workflow) { + return; + } + + // TODO infer from steps + let type: T | undefined; + for (const targetType of Object.values(WorkflowType)) { + const missing = workflow.steps.some((step) => !step.types.includes(targetType)); + if (!missing) { + type = targetType as unknown as T; + break; + } + } + + if (!type) { + throw new Error('Unable to infer workflow event type from steps'); + } + + const handler = getHandler(type); + if (!handler) { + this.logger.error(`Misconfigured workflow ${workflowId}: no handler for type ${type}`); + return; + } + + try { + const { read, write } = handler; + const readResult = await read(type); + let data = readResult.data; + for (const step of workflow.steps) { + const payload: WorkflowEventPayload = { + trigger: workflow.trigger, + type, + config: step.config ?? {}, + workflow: { + id: workflowId, + authToken: this.sign(readResult.authUserId), + stepId: step.id, + }, + data, + }; + + if (step.methodName.startsWith('noop')) { + continue; + } + + const result = await this.pluginRepository.callMethod>( + { + pluginKey: this.getPluginKey({ id: step.pluginId, hostFunctions: step.hostFunctions }), + methodName: step.methodName, + }, + payload, + ); + if (result?.changes) { + await write( + { + user: { + id: readResult.authUserId, + }, + session: { + id: DummyValue.UUID, + hasElevatedPermission: true, + }, + } as AuthDto, + result.changes, + ); + ({ data } = await read(type)); + } + + if (result?.config) { + await this.workflowRepository.updateStep(step.id, { config: result.config }); + } + + const shouldContinue = result?.workflow?.continue ?? true; + if (!shouldContinue) { + break; + } + } + + this.logger.debug(`Workflow ${workflowId} executed successfully`); + } catch (error) { + this.logger.error(`Error executing workflow ${workflowId}:`, error); + return JobStatus.Failed; + } + } +} diff --git a/server/src/services/workflow.service.ts b/server/src/services/workflow.service.ts index 1a65182b1f..850b4ac086 100644 --- a/server/src/services/workflow.service.ts +++ b/server/src/services/workflow.service.ts @@ -1,159 +1,114 @@ +import { WorkflowStepConfig, WorkflowTrigger } from '@immich/plugin-sdk'; import { BadRequestException, Injectable } from '@nestjs/common'; -import { Workflow } from 'src/database'; import { AuthDto } from 'src/dtos/auth.dto'; import { - mapWorkflowAction, - mapWorkflowFilter, + mapWorkflow, + mapWorkflowShare, WorkflowCreateDto, WorkflowResponseDto, + WorkflowSearchDto, + WorkflowShareResponseDto, + WorkflowTriggerResponseDto, WorkflowUpdateDto, } from 'src/dtos/workflow.dto'; -import { Permission, PluginContext, PluginTriggerType } from 'src/enum'; -import { pluginTriggers } from 'src/plugins'; - +import { Permission } from 'src/enum'; +import { PluginMethodSearchResponse } from 'src/repositories/plugin.repository'; import { BaseService } from 'src/services/base.service'; +import { getWorkflowTriggers, isMethodCompatible, resolveMethod } from 'src/utils/workflow'; @Injectable() export class WorkflowService extends BaseService { - async create(auth: AuthDto, dto: WorkflowCreateDto): Promise { - const context = this.getContextForTrigger(dto.triggerType); - - const filterInserts = await this.validateAndMapFilters(dto.filters, context); - const actionInserts = await this.validateAndMapActions(dto.actions, context); - - const workflow = await this.workflowRepository.createWorkflow( - { - ownerId: auth.user.id, - triggerType: dto.triggerType, - name: dto.name, - description: dto.description || '', - enabled: dto.enabled ?? true, - }, - filterInserts, - actionInserts, - ); - - return this.mapWorkflow(workflow); + getTriggers(): WorkflowTriggerResponseDto[] { + return getWorkflowTriggers(); } - async getAll(auth: AuthDto): Promise { - const workflows = await this.workflowRepository.getWorkflowsByOwner(auth.user.id); - - return Promise.all(workflows.map((workflow) => this.mapWorkflow(workflow))); + async search(auth: AuthDto, dto: WorkflowSearchDto): Promise { + const workflows = await this.workflowRepository.search({ ...dto, userId: auth.user.id }); + return workflows.map((workflow) => mapWorkflow(workflow)); } async get(auth: AuthDto, id: string): Promise { await this.requireAccess({ auth, permission: Permission.WorkflowRead, ids: [id] }); const workflow = await this.findOrFail(id); - return this.mapWorkflow(workflow); + return mapWorkflow(workflow); + } + + async share(auth: AuthDto, id: string): Promise { + await this.requireAccess({ auth, permission: Permission.WorkflowRead, ids: [id] }); + const workflow = await this.findOrFail(id); + return mapWorkflowShare(workflow); + } + + async create(auth: AuthDto, dto: WorkflowCreateDto): Promise { + const { steps: stepsDto, ...workflowDto } = dto; + const steps = await this.resolveAndValidateSteps(stepsDto ?? [], workflowDto.trigger); + + const workflow = await this.workflowRepository.create( + { + ...workflowDto, + ownerId: auth.user.id, + }, + steps.map((step) => ({ + enabled: step.enabled ?? true, + config: step.config as WorkflowStepConfig, + pluginMethodId: step.pluginMethod.id, + })), + ); + + return mapWorkflow({ ...workflow, steps: [] }); } async update(auth: AuthDto, id: string, dto: WorkflowUpdateDto): Promise { await this.requireAccess({ auth, permission: Permission.WorkflowUpdate, ids: [id] }); - if (Object.values(dto).filter((prop) => prop !== undefined).length === 0) { - throw new BadRequestException('No fields to update'); - } - - const workflow = await this.findOrFail(id); - const context = this.getContextForTrigger(dto.triggerType ?? workflow.triggerType); - - const { filters, actions, ...workflowUpdate } = dto; - const filterInserts = filters && (await this.validateAndMapFilters(filters, context)); - const actionInserts = actions && (await this.validateAndMapActions(actions, context)); - - const updatedWorkflow = await this.workflowRepository.updateWorkflow( + const { steps: stepsDto, ...workflowDto } = dto; + const current = await this.findOrFail(id); + const steps = stepsDto ? await this.resolveAndValidateSteps(stepsDto, dto.trigger ?? current.trigger) : undefined; + const workflow = await this.workflowRepository.update( id, - workflowUpdate, - filterInserts, - actionInserts, + workflowDto, + steps?.map((step) => ({ + enabled: step.enabled ?? true, + config: step.config as WorkflowStepConfig, + pluginMethodId: step.pluginMethod.id, + })), ); - return this.mapWorkflow(updatedWorkflow); + return mapWorkflow(workflow); } async delete(auth: AuthDto, id: string): Promise { await this.requireAccess({ auth, permission: Permission.WorkflowDelete, ids: [id] }); - await this.workflowRepository.deleteWorkflow(id); + await this.workflowRepository.delete(id); } - private async validateAndMapFilters( - filters: Array<{ pluginFilterId: string; filterConfig?: any }>, - requiredContext: PluginContext, - ) { - for (const dto of filters) { - const filter = await this.pluginRepository.getFilter(dto.pluginFilterId); - if (!filter) { - throw new BadRequestException(`Invalid filter ID: ${dto.pluginFilterId}`); + private async resolveAndValidateSteps(steps: T[], trigger: WorkflowTrigger) { + const methods = await this.pluginRepository.getForValidation(); + const results: Array = []; + + for (const step of steps) { + const pluginMethod = resolveMethod(methods, step.method); + if (!pluginMethod) { + throw new BadRequestException(`Unknown method ${step.method}`); } - if (!filter.supportedContexts.includes(requiredContext)) { - throw new BadRequestException( - `Filter "${filter.title}" does not support ${requiredContext} context. Supported contexts: ${filter.supportedContexts.join(', ')}`, - ); + if (!isMethodCompatible(pluginMethod, trigger)) { + throw new BadRequestException(`Method "${step.method}" is incompatible with workflow trigger: "${trigger}"`); } + + results.push({ ...step, pluginMethod }); } - return filters.map((dto, index) => ({ - pluginFilterId: dto.pluginFilterId, - filterConfig: dto.filterConfig || null, - order: index, - })); - } + // TODO make sure all steps can use a common WorkflowType - private async validateAndMapActions( - actions: Array<{ pluginActionId: string; actionConfig?: any }>, - requiredContext: PluginContext, - ) { - for (const dto of actions) { - const action = await this.pluginRepository.getAction(dto.pluginActionId); - if (!action) { - throw new BadRequestException(`Invalid action ID: ${dto.pluginActionId}`); - } - if (!action.supportedContexts.includes(requiredContext)) { - throw new BadRequestException( - `Action "${action.title}" does not support ${requiredContext} context. Supported contexts: ${action.supportedContexts.join(', ')}`, - ); - } - } - - return actions.map((dto, index) => ({ - pluginActionId: dto.pluginActionId, - actionConfig: dto.actionConfig || null, - order: index, - })); - } - - private getContextForTrigger(type: PluginTriggerType) { - const trigger = pluginTriggers.find((t) => t.type === type); - if (!trigger) { - throw new BadRequestException(`Invalid trigger type: ${type}`); - } - return trigger.contextType; + return results; } private async findOrFail(id: string) { - const workflow = await this.workflowRepository.getWorkflow(id); + const workflow = await this.workflowRepository.get(id); if (!workflow) { throw new BadRequestException('Workflow not found'); } return workflow; } - - private async mapWorkflow(workflow: Workflow): Promise { - const filters = await this.workflowRepository.getFilters(workflow.id); - const actions = await this.workflowRepository.getActions(workflow.id); - - return { - id: workflow.id, - ownerId: workflow.ownerId, - triggerType: workflow.triggerType, - name: workflow.name, - description: workflow.description, - createdAt: workflow.createdAt.toISOString(), - enabled: workflow.enabled, - filters: filters.map((f) => mapWorkflowFilter(f)), - actions: actions.map((a) => mapWorkflowAction(a)), - }; - } } diff --git a/server/src/types.ts b/server/src/types.ts index 86ba0a1cc2..4e5a383cca 100644 --- a/server/src/types.ts +++ b/server/src/types.ts @@ -1,7 +1,8 @@ +import { WorkflowTrigger } from '@immich/plugin-sdk'; import { ShallowDehydrateObject } from 'kysely'; import { SystemConfig } from 'src/config'; import { VECTOR_EXTENSIONS } from 'src/constants'; -import { Asset, AssetFile } from 'src/database'; +import { AssetFile } from 'src/database'; import { UploadFieldName } from 'src/dtos/asset-media.dto'; import { AuthDto } from 'src/dtos/auth.dto'; import { AssetEditActionItem } from 'src/dtos/editing.dto'; @@ -22,18 +23,18 @@ import { ImageFormat, JobName, MemoryType, - PluginTriggerType, QueueName, StorageFolder, SyncEntityType, SystemMetadataKey, TranscodeTarget, UserMetadataKey, - VideoCodec, + WorkflowType, } from 'src/enum'; -export type DeepPartial = - T extends Record +export type DeepPartial = T extends Date + ? T + : T extends Record ? { [K in keyof T]?: DeepPartial } : T extends Array ? DeepPartial[] @@ -89,26 +90,26 @@ export interface VideoStreamInfo { height: number; width: number; rotation: number; - codecName?: string; - profile?: H264Profile | HevcProfile | Av1Profile; - level?: number; + codecName: string | null; + profile: H264Profile | HevcProfile | Av1Profile | null; + level: number | null; frameCount: number; - frameRate?: number; - timeBase?: number; + frameRate: number | null; + timeBase: number | null; bitrate: number; pixelFormat: string; colorPrimaries: ColorPrimaries; colorMatrix: ColorMatrix; colorTransfer: ColorTransfer; - dvProfile?: DvProfile; - dvLevel?: number; - dvBlSignalCompatibilityId?: DvSignalCompatibility; + dvProfile: DvProfile | null; + dvLevel: number | null; + dvBlSignalCompatibilityId: DvSignalCompatibility | null; } export interface AudioStreamInfo { index: number; - codecName?: string; - profile?: AacProfile; + codecName: string | null; + profile: AacProfile | null; bitrate: number; } @@ -160,6 +161,25 @@ export interface TranscodeCommand { }; } +export interface VideoTuning { + strictGop: boolean; + lowLatency: boolean; +} + +export interface HlsCommandOptions { + initFilename: string; + inputPath: string; + packetCount: number; + playlistFilename: string; + seekSeconds?: number; + segmentDuration: number; + segmentFilename: string; + startSegment: number; + target: TranscodeTarget; + timeBase: number; + totalDuration: number; +} + export interface BitrateDistribution { max: number; target: number; @@ -175,14 +195,11 @@ export interface ImageBuffer { export interface VideoCodecSWConfig { getCommand( target: TranscodeTarget, - videoStream: VideoStreamInfo, - audioStream?: AudioStreamInfo, + video: VideoStreamInfo, + audio?: AudioStreamInfo, format?: VideoFormat, ): TranscodeCommand; -} - -export interface VideoCodecHWConfig extends VideoCodecSWConfig { - getSupportedCodecs(): Array; + getHlsCommand(options: HlsCommandOptions, video: VideoStreamInfo, audio?: AudioStreamInfo): string[]; } export interface ProbeOptions { @@ -288,22 +305,11 @@ export interface INotifyAlbumUpdateJob extends IEntityJob, IDelayedJob { recipientId: string; } -export interface WorkflowData { - [PluginTriggerType.AssetCreate]: { - userId: string; - asset: Asset; - }; - [PluginTriggerType.PersonRecognized]: { - personId: string; - assetId: string; - }; -} - -export interface IWorkflowJob { +export type IWorkflowJob = { id: string; + trigger: WorkflowTrigger; type: T; - event: WorkflowData[T]; -} +}; export interface JobCounts { active: number; @@ -380,6 +386,7 @@ export type JobItem = // Cleanup | { name: JobName.SessionCleanup; data?: IBaseJob } + | { name: JobName.HlsSessionCleanup; data?: IBaseJob } // Tags | { name: JobName.TagCleanup; data?: IBaseJob } @@ -413,7 +420,7 @@ export type JobItem = | { name: JobName.Ocr; data: IEntityJob } // Workflow - | { name: JobName.WorkflowRun; data: IWorkflowJob } + | { name: JobName.WorkflowAssetTrigger; data: { workflowId: string; assetId: string } } // Editor | { name: JobName.AssetEditThumbnailGeneration; data: IEntityJob }; @@ -532,6 +539,7 @@ export type UserPreferences = { people: { enabled: boolean; sidebarWeb: boolean; + minimumFaces: number; }; ratings: { enabled: boolean; @@ -574,3 +582,20 @@ export interface UserMetadata extends Record = T | ShallowDehydrateObject; + +export type JSONSchemaType = 'string' | 'number' | 'integer' | 'boolean' | 'object'; + +export type JSONSchemaProperty = { + type: JSONSchemaType; + description?: string; + default?: any; + enum?: string[]; + array?: boolean; + properties?: Record; + required?: string[]; +}; + +// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type +export interface ClassConstructor extends Function { + new (...args: any[]): T; +} diff --git a/server/src/types/plugin-schema.types.ts b/server/src/types/plugin-schema.types.ts deleted file mode 100644 index da1f6da935..0000000000 --- a/server/src/types/plugin-schema.types.ts +++ /dev/null @@ -1,56 +0,0 @@ -/** - * JSON Schema types for plugin configuration schemas - * Based on JSON Schema Draft 7 - */ - -import z from 'zod'; - -const JSONSchemaTypeSchema = z - .enum(['string', 'number', 'integer', 'boolean', 'object', 'array', 'null']) - .meta({ id: 'PluginJsonSchemaType' }); - -const JSONSchemaPropertySchema = z - .object({ - type: JSONSchemaTypeSchema.optional(), - description: z.string().optional(), - default: z.any().optional(), - enum: z.array(z.string()).optional(), - - get items() { - return JSONSchemaPropertySchema.optional(); - }, - - get properties() { - return z.record(z.string(), JSONSchemaPropertySchema).optional(); - }, - - required: z.array(z.string()).optional(), - - get additionalProperties() { - return z.union([z.boolean(), JSONSchemaPropertySchema]).optional(); - }, - }) - .meta({ id: 'PluginJsonSchemaProperty' }); - -export type JSONSchemaProperty = z.infer; - -export const JSONSchemaSchema = z - .object({ - type: JSONSchemaTypeSchema.optional(), - properties: z.record(z.string(), JSONSchemaPropertySchema).optional(), - required: z.array(z.string()).optional(), - additionalProperties: z.boolean().optional(), - description: z.string().optional(), - }) - .meta({ id: 'PluginJsonSchema' }); -export type JSONSchema = z.infer; - -type ConfigValue = string | number | boolean | null | ConfigValue[] | { [key: string]: ConfigValue }; - -const ConfigValueSchema: z.ZodType = z.any().meta({ id: 'PluginConfigValue' }); - -export const FilterConfigSchema = z.record(z.string(), ConfigValueSchema).meta({ id: 'WorkflowFilterConfig' }); -export type FilterConfig = z.infer; - -export const ActionConfigSchema = z.record(z.string(), ConfigValueSchema).meta({ id: 'WorkflowActionConfig' }); -export type ActionConfig = z.infer; diff --git a/server/src/utils/database.ts b/server/src/utils/database.ts index fdf1d98caf..cb942b5366 100644 --- a/server/src/utils/database.ts +++ b/server/src/utils/database.ts @@ -17,11 +17,11 @@ import { jsonArrayFrom, jsonObjectFrom } from 'kysely/helpers/postgres'; import { Notice, PostgresError } from 'postgres'; import { columns, lockableProperties, LockableProperty, Person } from 'src/database'; import { AssetEditActionItem } from 'src/dtos/editing.dto'; -import { AssetFileType, AssetVisibility, DatabaseExtension, ExifOrientation } from 'src/enum'; +import { AssetFileType, AssetOrderBy, AssetVisibility, DatabaseExtension, ExifOrientation } from 'src/enum'; import { AssetSearchBuilderOptions } from 'src/repositories/search.repository'; import { DB } from 'src/schema'; import { AssetExifTable } from 'src/schema/tables/asset-exif.table'; -import { AudioStreamInfo, VectorExtension, VideoFormat, VideoStreamInfo } from 'src/types'; +import { AudioStreamInfo, VectorExtension, VideoFormat, VideoPacketInfo, VideoStreamInfo } from 'src/types'; export const getKyselyConfig = (connection: DatabaseConnectionParams): KyselyConfig => { return { @@ -71,10 +71,13 @@ export const removeUndefinedKeys = (update: T, template: unkno }; export const ASSET_CHECKSUM_CONSTRAINT = 'UQ_assets_owner_checksum'; +export const VIDEO_STREAM_SESSION_PK_CONSTRAINT = 'video_stream_session_pkey'; -export const isAssetChecksumConstraint = (error: unknown) => { - return (error as PostgresError)?.constraint_name === 'UQ_assets_owner_checksum'; -}; +export const isAssetChecksumConstraint = (error: unknown) => + (error as PostgresError)?.constraint_name === ASSET_CHECKSUM_CONSTRAINT; + +export const isVideoStreamSessionPkConstraint = (error: unknown) => + (error as PostgresError)?.constraint_name === VIDEO_STREAM_SESSION_PK_CONSTRAINT; export function withDefaultVisibility(qb: SelectQueryBuilder) { return qb.where('asset.visibility', 'in', [sql.lit(AssetVisibility.Archive), sql.lit(AssetVisibility.Timeline)]); @@ -146,7 +149,7 @@ export function withVideoStream(eb: ExpressionBuilder(); + ).$castTo<(VideoStreamInfo & { timeBase: number }) | null>(); } export function withVideoFormat(eb: ExpressionBuilder) { @@ -158,6 +161,22 @@ export function withVideoFormat(eb: ExpressionBuilder(); } +export function withVideoPackets(eb: ExpressionBuilder) { + return jsonObjectFrom( + eb + .selectFrom(dummy) + .where('asset_keyframe.assetId', 'is not', sql.lit(null)) + .select([ + 'asset_keyframe.pts as keyframePts', + 'asset_keyframe.accDuration as keyframeAccDuration', + 'asset_keyframe.ownDuration as keyframeOwnDuration', + 'asset_keyframe.totalDuration', + 'asset_keyframe.packetCount', + 'asset_keyframe.outputFrames', + ]), + ).$castTo(); +} + export function withSmartSearch(qb: SelectQueryBuilder) { return qb .leftJoin('smart_search', 'asset.id', 'smart_search.assetId') @@ -282,8 +301,8 @@ export function withTags(eb: ExpressionBuilder) { ).as('tags'); } -export function truncatedDate() { - return sql`date_trunc(${sql.lit('MONTH')}, "localDateTime" AT TIME ZONE 'UTC') AT TIME ZONE 'UTC'`; +export function truncatedDate(order: AssetOrderBy = AssetOrderBy.TakenAt) { + return sql`date_trunc(${sql.lit('MONTH')}, ${sql.ref(order === AssetOrderBy.CreatedAt ? 'asset.createdAt' : 'localDateTime')} AT TIME ZONE 'UTC') AT TIME ZONE 'UTC'`; } export function withTagId(qb: SelectQueryBuilder, tagId: string) { diff --git a/server/src/utils/date.spec.ts b/server/src/utils/date.spec.ts new file mode 100644 index 0000000000..729b515467 --- /dev/null +++ b/server/src/utils/date.spec.ts @@ -0,0 +1,33 @@ +import { asDateString, asDateTimeString } from 'src/utils/date'; +import { describe, expect, it } from 'vitest'; + +describe('asDateString', () => { + it('should return null for null input', () => { + expect(asDateString(null)).toBeNull(); + }); + + it('should pass through a pre-serialized string unchanged', () => { + expect(asDateString('2000-01-15')).toBe('2000-01-15'); + }); + + it('should return the local calendar date, not the UTC date', () => { + const date = new Date(2000, 0, 15); // 15 Jan 2000, local midnight + expect(asDateString(date)).toBe('2000-01-15'); + }); +}); + +describe('asDateTimeString', () => { + it('should return null for null input', () => { + expect(asDateTimeString(null)).toBeNull(); + }); + + it('should pass through a pre-serialized string unchanged', () => { + const iso = '2000-01-15T12:00:00.000Z'; + expect(asDateTimeString(iso)).toBe(iso); + }); + + it('should return an ISO 8601 datetime string for a Date', () => { + const date = new Date('2000-01-15T12:00:00.000Z'); + expect(asDateTimeString(date)).toBe('2000-01-15T12:00:00.000Z'); + }); +}); diff --git a/server/src/utils/date.ts b/server/src/utils/date.ts index d4de1eba86..394bab8a02 100644 --- a/server/src/utils/date.ts +++ b/server/src/utils/date.ts @@ -1,23 +1,18 @@ import { DateTime } from 'luxon'; +import { isoDateToDate, isoDatetimeToDate } from 'src/validation'; /** * Convert a date to a ISO 8601 datetime string. - * @param x - The date to convert. - * @returns The ISO 8601 datetime string. - * @deprecated Remove this and all references when using `ZodSerializerDto` on the controllers. Then the codec in `isoDatetimeToDate` in validation.ts will handle the conversion instead. */ -export const asDateString = (x: T) => { - return x instanceof Date ? x.toISOString() : (x as Exclude); +export const asDateTimeString = (x: T) => { + return x instanceof Date ? isoDatetimeToDate.encode(x) : (x as Exclude); }; /** - * Convert a date to a date string. - * @param x - The date to convert. - * @returns The date string. - * @deprecated Remove this and all references when using `ZodSerializerDto` on the controllers. Then the codec in `isoDateToDate` in validation.ts will handle the conversion instead. + * Convert a date to a date string (yyyy-mm-dd). */ -export const asBirthDateString = (x: Date | string | null): string | null => { - return x instanceof Date ? x.toISOString().split('T')[0] : x; +export const asDateString = (x: Date | string | null): string | null => { + return x instanceof Date ? isoDateToDate.encode(x) : x; }; export const extractTimeZone = (dateTimeOriginal?: string | null) => { diff --git a/server/src/utils/event.ts b/server/src/utils/event.ts new file mode 100644 index 0000000000..fd791620de --- /dev/null +++ b/server/src/utils/event.ts @@ -0,0 +1,50 @@ +import { ArgOf, EmitEvent } from 'src/repositories/event.repository'; + +export class PendingEvents extends { error?: string } ? T : never }[EmitEvent]> { + private pending = new Map>[]; timeout: NodeJS.Timeout }>(); + private timeoutMs: number; + + constructor({ timeoutMs }: { timeoutMs: number }) { + this.timeoutMs = timeoutMs; + } + + wait(key: string): Promise> { + const completer = Promise.withResolvers>(); + const existing = this.pending.get(key); + if (existing) { + existing.completers.push(completer); + return completer.promise; + } + + const timeout = setTimeout(() => this.complete(key, { error: 'Request timed out' }), this.timeoutMs); + this.pending.set(key, { completers: [completer], timeout }); + return completer.promise; + } + + complete(key: string, value: ArgOf | { error: string }) { + const pending = this.pending.get(key); + if (!pending) { + return; + } + clearTimeout(pending.timeout); + this.pending.delete(key); + if ('error' in value) { + const error = new Error(value.error); + for (const completer of pending.completers) { + completer.reject(error); + } + } else { + for (const completer of pending.completers) { + completer.resolve(value); + } + } + } + + rejectByPrefix(prefix: string, error: string) { + for (const key of this.pending.keys()) { + if (key.startsWith(prefix)) { + this.complete(key, { error }); + } + } + } +} diff --git a/server/src/utils/logger.ts b/server/src/utils/logger.ts index ecc8847043..df321f24c2 100644 --- a/server/src/utils/logger.ts +++ b/server/src/utils/logger.ts @@ -1,14 +1,23 @@ import { HttpException } from '@nestjs/common'; +import { Request } from 'express'; import { LoggingRepository } from 'src/repositories/logging.repository'; -export const logGlobalError = (logger: LoggingRepository, error: Error) => { - if (error instanceof HttpException) { +const isRequestAborted = (request: Request) => request.destroyed === true && request.complete === false; +export const isHttpException = (error: Error): error is HttpException => error instanceof HttpException; + +export const onRequestError = (req: Request, error: Error, logger: LoggingRepository) => { + if (isHttpException(error)) { const status = error.getStatus(); const response = error.getResponse(); logger.debug(`HttpException(${status}): ${JSON.stringify(response)}`); return; } + if (isRequestAborted(req)) { + logger.debug(`Client aborted request: ${error}`); + return; + } + if (error instanceof Error) { logger.error(`Unknown error: ${error}`, error?.stack); return; diff --git a/server/src/utils/media.ts b/server/src/utils/media.ts index 49e11edab7..b3a617c36e 100644 --- a/server/src/utils/media.ts +++ b/server/src/utils/media.ts @@ -1,4 +1,4 @@ -import { AUDIO_ENCODER } from 'src/constants'; +import { AUDIO_ENCODER, SUPPORTED_HWA_CODECS } from 'src/constants'; import { SystemConfigFFmpegDto } from 'src/dtos/system-config.dto'; import { ColorMatrix, @@ -13,38 +13,56 @@ import { import { AudioStreamInfo, BitrateDistribution, + HlsCommandOptions, TranscodeCommand, - VideoCodecHWConfig, VideoCodecSWConfig, VideoFormat, VideoInterfaces, VideoStreamInfo, + VideoTuning, } from 'src/types'; +export const isVideoRotated = (videoStream: VideoStreamInfo): boolean => Math.abs(videoStream.rotation) === 90; + +export const isVideoVertical = (videoStream: VideoStreamInfo): boolean => + videoStream.height > videoStream.width || isVideoRotated(videoStream); + +export const getOutputSize = (videoStream: VideoStreamInfo, targetRes: number) => { + const factor = Math.max(videoStream.height, videoStream.width) / Math.min(videoStream.height, videoStream.width); + let larger = Math.round(targetRes * factor); + if (larger % 2 !== 0) { + larger -= 1; + } + return isVideoVertical(videoStream) ? { width: targetRes, height: larger } : { width: larger, height: targetRes }; +}; + export class BaseConfig implements VideoCodecSWConfig { readonly presets = ['veryslow', 'slower', 'slow', 'medium', 'fast', 'faster', 'veryfast', 'superfast', 'ultrafast']; - protected constructor(protected config: SystemConfigFFmpegDto) {} + protected constructor( + protected config: SystemConfigFFmpegDto, + protected tune: VideoTuning = { strictGop: false, lowLatency: false }, + ) {} - static create(config: SystemConfigFFmpegDto, interfaces: VideoInterfaces): VideoCodecSWConfig { + static create(config: SystemConfigFFmpegDto, interfaces: VideoInterfaces, tune?: VideoTuning) { if (config.accel === TranscodeHardwareAcceleration.Disabled) { - return this.getSWCodecConfig(config); + return this.getSWCodecConfig(config, tune); } - return this.getHWCodecConfig(config, interfaces); + return this.getHWCodecConfig(config, interfaces, tune); } - private static getSWCodecConfig(config: SystemConfigFFmpegDto) { + private static getSWCodecConfig(config: SystemConfigFFmpegDto, tune?: VideoTuning): VideoCodecSWConfig { switch (config.targetVideoCodec) { case VideoCodec.H264: { - return new H264Config(config); + return new H264Config(config, tune); } case VideoCodec.Hevc: { - return new HEVCConfig(config); + return new HEVCConfig(config, tune); } case VideoCodec.Vp9: { - return new VP9Config(config); + return new VP9Config(config, tune); } case VideoCodec.Av1: { - return new AV1Config(config); + return new AV1Config(config, tune); } default: { throw new Error(`Codec '${config.targetVideoCodec}' is unsupported`); @@ -52,72 +70,122 @@ export class BaseConfig implements VideoCodecSWConfig { } } - private static getHWCodecConfig(config: SystemConfigFFmpegDto, interfaces: VideoInterfaces) { - let handler: VideoCodecHWConfig; + private static getHWCodecConfig(config: SystemConfigFFmpegDto, interfaces: VideoInterfaces, tune?: VideoTuning) { + if (!SUPPORTED_HWA_CODECS[config.accel].includes(config.targetVideoCodec)) { + throw new Error( + `${config.accel.toUpperCase()} acceleration does not support codec '${config.targetVideoCodec.toUpperCase()}'. Supported codecs: ${SUPPORTED_HWA_CODECS[config.accel]}`, + ); + } + + let handler: VideoCodecSWConfig; switch (config.accel) { case TranscodeHardwareAcceleration.Nvenc: { handler = config.accelDecode - ? new NvencHwDecodeConfig(config, interfaces) - : new NvencSwDecodeConfig(config, interfaces); + ? new NvencHwDecodeConfig(config, interfaces, tune) + : new NvencSwDecodeConfig(config, interfaces, tune); break; } case TranscodeHardwareAcceleration.Qsv: { handler = config.accelDecode - ? new QsvHwDecodeConfig(config, interfaces) - : new QsvSwDecodeConfig(config, interfaces); + ? new QsvHwDecodeConfig(config, interfaces, tune) + : new QsvSwDecodeConfig(config, interfaces, tune); break; } case TranscodeHardwareAcceleration.Vaapi: { handler = config.accelDecode - ? new VaapiHwDecodeConfig(config, interfaces) - : new VaapiSwDecodeConfig(config, interfaces); + ? new VaapiHwDecodeConfig(config, interfaces, tune) + : new VaapiSwDecodeConfig(config, interfaces, tune); break; } case TranscodeHardwareAcceleration.Rkmpp: { handler = config.accelDecode - ? new RkmppHwDecodeConfig(config, interfaces) - : new RkmppSwDecodeConfig(config, interfaces); + ? new RkmppHwDecodeConfig(config, interfaces, tune) + : new RkmppSwDecodeConfig(config, interfaces, tune); break; } default: { throw new Error(`${config.accel.toUpperCase()} acceleration is unsupported`); } } - if (!handler.getSupportedCodecs().includes(config.targetVideoCodec)) { - throw new Error( - `${config.accel.toUpperCase()} acceleration does not support codec '${config.targetVideoCodec.toUpperCase()}'. Supported codecs: ${handler.getSupportedCodecs()}`, - ); - } return handler; } - getCommand( - target: TranscodeTarget, - videoStream: VideoStreamInfo, - audioStream?: AudioStreamInfo, - format?: VideoFormat, - ) { + getCommand(target: TranscodeTarget, video: VideoStreamInfo, audio?: AudioStreamInfo, format?: VideoFormat) { const options = { - inputOptions: this.getBaseInputOptions(videoStream, format), - outputOptions: [...this.getBaseOutputOptions(target, videoStream, audioStream), '-v', 'verbose'], + inputOptions: this.getBaseInputOptions(video, format), + outputOptions: [ + ...this.getBaseOutputOptions(target, video, audio), + ...this.getPresetOptions(), + ...this.getBitrateOptions(), + ...this.getEncoderOptions(), + '-movflags', + 'faststart', + '-fps_mode', + 'passthrough', + '-v', + 'verbose', + ], twoPass: this.eligibleForTwoPass(), - progress: { frameCount: videoStream.frameCount, percentInterval: 5 }, + progress: { frameCount: video.frameCount, percentInterval: 5 }, } as TranscodeCommand; if ([TranscodeTarget.All, TranscodeTarget.Video].includes(target)) { - const filters = this.getFilterOptions(videoStream); + const filters = this.getFilterOptions(video); if (filters.length > 0) { options.outputOptions.push('-vf', filters.join(',')); } } - options.outputOptions.push( + return options; + } + + getHlsCommand(options: HlsCommandOptions, video: VideoStreamInfo, audio?: AudioStreamInfo) { + const args: string[] = this.getBaseInputOptions(video); + if (options.seekSeconds) { + args.push('-ss', String(options.seekSeconds)); + } + args.push( + '-nostdin', + '-nostats', + '-i', + options.inputPath, + ...this.getBaseOutputOptions(options.target, video, audio), ...this.getPresetOptions(), - ...this.getOutputThreadOptions(), ...this.getBitrateOptions(), + ...this.getEncoderOptions(), + '-copyts', + '-r', + `${options.packetCount * options.timeBase}/${options.totalDuration}`, + '-avoid_negative_ts', + 'disabled', + '-f', + 'hls', + '-hls_time', + String(options.segmentDuration), + '-hls_list_size', + '0', + '-hls_segment_type', + 'fmp4', + '-hls_fmp4_init_filename', + options.initFilename, + '-hls_segment_options', + 'movflags=+frag_discont', + '-hls_flags', + 'temp_file', + '-hls_segment_filename', + options.segmentFilename, + '-start_number', + String(options.startSegment), ); - return options; + if ([TranscodeTarget.All, TranscodeTarget.Video].includes(options.target)) { + const filters = this.getFilterOptions(video); + if (filters.length > 0) { + args.push('-vf', filters.join(',')); + } + } + args.push(options.playlistFilename); + return args; } // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -129,23 +197,7 @@ export class BaseConfig implements VideoCodecSWConfig { const videoCodec = [TranscodeTarget.All, TranscodeTarget.Video].includes(target) ? this.getVideoCodec() : 'copy'; const audioCodec = [TranscodeTarget.All, TranscodeTarget.Audio].includes(target) ? this.getAudioEncoder() : 'copy'; - const options = [ - '-c:v', - videoCodec, - '-c:a', - audioCodec, - // Makes a second pass moving the moov atom to the - // beginning of the file for improved playback speed. - '-movflags', - 'faststart', - '-fps_mode', - 'passthrough', - '-map', - `0:${videoStream.index}`, - '-map_metadata', - '-1', - ]; - + const options = ['-c:v', videoCodec, '-c:a', audioCodec, '-map', `0:${videoStream.index}`, '-map_metadata', '-1']; if (audioStream) { options.push('-map', `0:${audioStream.index}`); } @@ -157,18 +209,22 @@ export class BaseConfig implements VideoCodecSWConfig { } if (this.getGopSize() > 0) { options.push('-g', `${this.getGopSize()}`); + if (this.tune.strictGop) { + options.push('-keyint_min', `${this.getGopSize()}`); + } } - - if ( - this.config.targetVideoCodec === VideoCodec.Hevc && - (videoCodec !== 'copy' || videoStream.codecName === 'hevc') - ) { + const isHvc = (videoCodec === 'copy' ? videoStream.codecName : videoCodec) === VideoCodec.Hevc; + if (isHvc) { options.push('-tag:v', 'hvc1'); } return options; } + getEncoderOptions(): string[] { + return []; + } + getFilterOptions(videoStream: VideoStreamInfo) { const options = []; if (this.shouldScale(videoStream)) { @@ -272,25 +328,7 @@ export class BaseConfig implements VideoCodecSWConfig { getScaling(videoStream: VideoStreamInfo, mult = 2) { const targetResolution = this.getTargetResolution(videoStream); - return this.isVideoVertical(videoStream) ? `${targetResolution}:-${mult}` : `-${mult}:${targetResolution}`; - } - - getSize(videoStream: VideoStreamInfo) { - const smaller = this.getTargetResolution(videoStream); - const factor = Math.max(videoStream.height, videoStream.width) / Math.min(videoStream.height, videoStream.width); - let larger = Math.round(smaller * factor); - if (larger % 2 !== 0) { - larger -= 1; - } - return this.isVideoVertical(videoStream) ? { width: smaller, height: larger } : { width: larger, height: smaller }; - } - - isVideoRotated(videoStream: VideoStreamInfo) { - return Math.abs(videoStream.rotation) === 90; - } - - isVideoVertical(videoStream: VideoStreamInfo) { - return videoStream.height > videoStream.width || this.isVideoRotated(videoStream); + return isVideoVertical(videoStream) ? `${targetResolution}:-${mult}` : `-${mult}:${targetResolution}`; } isBitrateConstrained() { @@ -353,23 +391,18 @@ export class BaseConfig implements VideoCodecSWConfig { } } -export class BaseHWConfig extends BaseConfig implements VideoCodecHWConfig { +export class BaseHWConfig extends BaseConfig { protected device: string; - protected interfaces: VideoInterfaces; constructor( protected config: SystemConfigFFmpegDto, - interfaces: VideoInterfaces, + protected interfaces: VideoInterfaces, + tune?: VideoTuning, ) { - super(config); - this.interfaces = interfaces; + super(config, tune); this.device = this.getDevice(interfaces); } - getSupportedCodecs() { - return [VideoCodec.H264, VideoCodec.Hevc]; - } - validateDevices(devices: string[]) { if (devices.length === 0) { throw new Error('No /dev/dri devices found. If using Docker, make sure at least one /dev/dri device is mounted'); @@ -474,24 +507,32 @@ export class ThumbnailConfig extends BaseConfig { } export class H264Config extends BaseConfig { - getOutputThreadOptions() { - const options = super.getOutputThreadOptions(); - if (this.config.threads === 1) { - options.push('-x264-params', 'frame-threads=1:pools=none'); + getEncoderOptions(): string[] { + const out = this.getOutputThreadOptions(); + if (this.tune.strictGop) { + out.push('-sc_threshold:v', '0'); } - - return options; + if (this.config.threads === 1) { + out.push('-x264-params', 'frame-threads=1:pools=none'); + } + return out; } } export class HEVCConfig extends BaseConfig { - getOutputThreadOptions() { - const options = super.getOutputThreadOptions(); - if (this.config.threads === 1) { - options.push('-x265-params', 'frame-threads=1:pools=none'); + getEncoderOptions(): string[] { + const out: string[] = this.getOutputThreadOptions(); + const params: string[] = []; + if (this.tune.strictGop) { + params.push('no-scenecut=1', 'no-open-gop=1'); } - - return options; + if (this.config.threads === 1) { + params.push('frame-threads=1', 'pools=none'); + } + if (params.length > 0) { + out.push('-x265-params', params.join(':')); + } + return out; } } @@ -520,8 +561,8 @@ export class VP9Config extends BaseConfig { return [`-${this.useCQP() ? 'q:v' : 'crf'}`, `${this.config.crf}`, '-b:v', `${bitrates.max}${bitrates.unit}`]; } - getOutputThreadOptions() { - return ['-row-mt', '1', ...super.getOutputThreadOptions()]; + getEncoderOptions(): string[] { + return ['-row-mt', '1', ...this.getOutputThreadOptions()]; } eligibleForTwoPass() { @@ -543,23 +584,22 @@ export class AV1Config extends BaseConfig { } getBitrateOptions() { - const options = ['-crf', `${this.config.crf}`]; - const bitrates = this.getBitrateDistribution(); - const svtparams = []; - if (this.config.threads > 0) { - svtparams.push(`lp=${this.config.threads}`); - } - if (bitrates.max > 0) { - svtparams.push(`mbr=${bitrates.max}${bitrates.unit}`); - } - if (svtparams.length > 0) { - options.push('-svtav1-params', svtparams.join(':')); - } - return options; + return ['-crf', `${this.config.crf}`]; } - getOutputThreadOptions() { - return []; // Already set above with svtav1-params + getEncoderOptions(): string[] { + const params: string[] = []; + if (this.tune.lowLatency) { + params.push('hierarchical-levels=3', 'lookahead=0', 'enable-tf=0'); + } + if (this.config.threads > 0) { + params.push(`lp=${this.config.threads}`); + } + const bitrates = this.getBitrateDistribution(); + if (bitrates.max > 0) { + params.push(`mbr=${bitrates.max}${bitrates.unit}`); + } + return params.length > 0 ? ['-svtav1-params', params.join(':')] : []; } eligibleForTwoPass() { @@ -572,10 +612,6 @@ export class NvencSwDecodeConfig extends BaseHWConfig { return '0'; } - getSupportedCodecs() { - return [VideoCodec.H264, VideoCodec.Hevc, VideoCodec.Av1]; - } - getBaseInputOptions() { return ['-init_hw_device', `cuda=cuda:${this.device}`, '-filter_hw_device', 'cuda']; } @@ -652,6 +688,14 @@ export class NvencSwDecodeConfig extends BaseHWConfig { return []; } + getEncoderOptions(): string[] { + const out = this.getOutputThreadOptions(); + if (this.tune.strictGop) { + out.push('-forced-idr', '1'); + } + return out; + } + getRefs() { const bframes = this.getBFrames(); if (bframes > 0 && bframes < 3 && this.config.refs < 3) { @@ -703,8 +747,8 @@ export class NvencHwDecodeConfig extends NvencSwDecodeConfig { return ['-threads', '1']; } - getOutputThreadOptions() { - return []; + getEncoderOptions(): string[] { + return this.tune.strictGop ? ['-forced-idr', '1'] : []; } } @@ -749,10 +793,6 @@ export class QsvSwDecodeConfig extends BaseHWConfig { return options; } - getSupportedCodecs() { - return [VideoCodec.H264, VideoCodec.Hevc, VideoCodec.Vp9, VideoCodec.Av1]; - } - // recommended from https://github.com/intel/media-delivery/blob/master/doc/benchmarks/intel-iris-xe-max-graphics/intel-iris-xe-max-graphics.md getBFrames() { if (this.config.bframes < 0) { @@ -775,6 +815,14 @@ export class QsvSwDecodeConfig extends BaseHWConfig { getScaling(videoStream: VideoStreamInfo): string { return super.getScaling(videoStream, 1); } + + getEncoderOptions(): string[] { + const out = this.getOutputThreadOptions(); + if (this.tune.strictGop) { + out.push('-idr_interval', '0'); + } + return out; + } } export class QsvHwDecodeConfig extends QsvSwDecodeConfig { @@ -888,13 +936,17 @@ export class VaapiSwDecodeConfig extends BaseHWConfig { return options; } - getSupportedCodecs() { - return [VideoCodec.H264, VideoCodec.Hevc, VideoCodec.Vp9, VideoCodec.Av1]; - } - useCQP() { return this.config.cqMode !== CQMode.Icq || this.config.targetVideoCodec === VideoCodec.Vp9; } + + getEncoderOptions(): string[] { + const out = this.getOutputThreadOptions(); + if (this.tune.strictGop) { + out.push('-idr_interval', '0'); + } + return out; + } } export class VaapiHwDecodeConfig extends VaapiSwDecodeConfig { @@ -988,10 +1040,6 @@ export class RkmppSwDecodeConfig extends BaseHWConfig { return ['-rc_mode', 'CQP', '-qp_init', `${this.config.crf}`]; } - getSupportedCodecs() { - return [VideoCodec.H264, VideoCodec.Hevc]; - } - getVideoCodec(): string { return `${this.config.targetVideoCodec}_rkmpp`; } diff --git a/server/src/utils/misc.ts b/server/src/utils/misc.ts index 450563cf7e..efcb509941 100644 --- a/server/src/utils/misc.ts +++ b/server/src/utils/misc.ts @@ -1,16 +1,12 @@ import { INestApplication } from '@nestjs/common'; import { + ApiBodyOptions, DocumentBuilder, OpenAPIObject, SwaggerCustomOptions, SwaggerDocumentOptions, SwaggerModule, } from '@nestjs/swagger'; -import { - OperationObject, - ReferenceObject, - SchemaObject, -} from '@nestjs/swagger/dist/interfaces/open-api-spec.interface'; import _ from 'lodash'; import { cleanupOpenApiDoc } from 'nestjs-zod'; import { writeFileSync } from 'node:fs'; @@ -19,10 +15,15 @@ import picomatch from 'picomatch'; import parse from 'picomatch/lib/parse'; import { SystemConfig } from 'src/config'; import { CLIP_MODEL_INFO, endpointTags, serverVersion } from 'src/constants'; -import { extraSyncModels } from 'src/dtos/sync.dto'; +import { extraModels } from 'src/decorators'; import { ApiCustomExtension, ImmichCookie, ImmichHeader, MetadataKey } from 'src/enum'; import { LoggingRepository } from 'src/repositories/logging.repository'; +type OperationObject = NonNullable; +type ReferenceOrSchemaObject = Extract['schema']; +type ReferenceObject = Extract; +type SchemaObject = Exclude; + export class ImmichStartupError extends Error {} export const isStartUpError = (error: unknown): error is ImmichStartupError => error instanceof ImmichStartupError; @@ -288,7 +289,7 @@ export const useSwagger = (app: INestApplication, { write }: { write: boolean }) const options: SwaggerDocumentOptions = { operationIdFactory: (controllerKey: string, methodKey: string) => methodKey, - extraModels: extraSyncModels, + extraModels, ignoreGlobalPrefix: true, }; diff --git a/server/src/utils/preferences.ts b/server/src/utils/preferences.ts index b25369670a..6b67398d23 100644 --- a/server/src/utils/preferences.ts +++ b/server/src/utils/preferences.ts @@ -21,6 +21,7 @@ const getDefaultPreferences = (): UserPreferences => { people: { enabled: true, sidebarWeb: false, + minimumFaces: 3, }, sharedLinks: { enabled: true, diff --git a/server/src/utils/workflow.spec.ts b/server/src/utils/workflow.spec.ts new file mode 100644 index 0000000000..5defe92d90 --- /dev/null +++ b/server/src/utils/workflow.spec.ts @@ -0,0 +1,37 @@ +import { WorkflowTrigger } from '@immich/plugin-sdk'; +import { WorkflowType } from 'src/enum'; +import { isMethodCompatible } from 'src/utils/workflow'; + +const tests: Array<{ trigger: WorkflowTrigger; types: WorkflowType[]; expected: boolean }> = [ + { + trigger: WorkflowTrigger.AssetCreate, + types: [WorkflowType.AssetV1], + expected: true, + }, + { + trigger: WorkflowTrigger.AssetCreate, + types: [WorkflowType.AssetPersonV1], + expected: true, + }, + { + trigger: WorkflowTrigger.PersonRecognized, + types: [WorkflowType.AssetPersonV1], + expected: true, + }, + { + trigger: WorkflowTrigger.PersonRecognized, + types: [WorkflowType.AssetV1], + expected: false, + }, + { + trigger: WorkflowTrigger.PersonRecognized, + types: [WorkflowType.AssetV1, WorkflowType.AssetPersonV1], + expected: true, + }, +]; + +describe(isMethodCompatible.name, () => { + it.each(tests)('should return $expected for trigger $trigger with types $types', ({ trigger, types, expected }) => { + expect(isMethodCompatible({ types }, trigger)).toBe(expected); + }); +}); diff --git a/server/src/utils/workflow.ts b/server/src/utils/workflow.ts new file mode 100644 index 0000000000..5383db818e --- /dev/null +++ b/server/src/utils/workflow.ts @@ -0,0 +1,70 @@ +import { WorkflowTrigger } from '@immich/plugin-sdk'; +import { WorkflowType } from 'src/enum'; +import { PluginMethodSearchResponse } from 'src/repositories/plugin.repository'; + +export const triggerMap: Record = { + [WorkflowTrigger.AssetCreate]: [WorkflowType.AssetV1], + [WorkflowTrigger.PersonRecognized]: [WorkflowType.AssetPersonV1], + [WorkflowTrigger.AssetMetadataExtraction]: [WorkflowType.AssetV1], +}; + +export const getWorkflowTriggers = () => + Object.entries(triggerMap).map(([trigger, types]) => ({ trigger: trigger as WorkflowTrigger, types })); + +/** some types extend other types and have implied compatibility */ +const inferredMap: Record = { + [WorkflowType.AssetV1]: [], + [WorkflowType.AssetPersonV1]: [WorkflowType.AssetV1], +}; + +const withImpliedItems = (type: WorkflowType): WorkflowType[] => { + const childTypes = inferredMap[type]; + const results = [type]; + for (const child of childTypes) { + results.push(...withImpliedItems(child)); + } + + return results; +}; + +export const isMethodCompatible = (pluginMethod: { types: WorkflowType[] }, trigger: WorkflowTrigger) => { + const validTypes = triggerMap[trigger]; + const pluginCompatibility = pluginMethod.types.map((type) => withImpliedItems(type)); + for (const requested of validTypes) { + for (const pluginCompatibilityGroup of pluginCompatibility) { + if (pluginCompatibilityGroup.includes(requested)) { + return true; + } + } + } + + return false; +}; + +export const resolveMethod = (methods: PluginMethodSearchResponse[], method: string) => { + const result = parseMethodString(method); + if (!result) { + return; + } + + const { pluginName, methodName } = result; + + return methods.find((method) => method.pluginName === pluginName && method.name === methodName); +}; + +export const asPluginKey = (method: { pluginName: string; name: string }) => { + return `${method.pluginName}#${method.name}`; +}; + +const METHOD_REGEX = /^(?[^@#\s]+)(?:@(?[^#\s]*))?#(?[^@#\s]+)$/; +export const parseMethodString = (method: string) => { + const matches = METHOD_REGEX.exec(method); + if (!matches) { + return; + } + + const pluginName = matches.groups?.name; + const version = matches.groups?.version; + const methodName = matches.groups?.method; + return { pluginName, version, methodName }; +}; diff --git a/server/src/validation.ts b/server/src/validation.ts index 59131b3abe..95bfe003a4 100644 --- a/server/src/validation.ts +++ b/server/src/validation.ts @@ -125,11 +125,6 @@ const FilenameParamSchema = z.object({ export class FilenameParamDto extends createZodDto(FilenameParamSchema) {} -export const isValidInteger = (value: number, options: { min?: number; max?: number }): value is number => { - const { min = Number.MIN_SAFE_INTEGER, max = Number.MAX_SAFE_INTEGER } = options; - return Number.isInteger(value) && value >= min && value <= max; -}; - /** * Unified email validation * Converts email strings to lowercase and validates against HTML5 email regex @@ -171,7 +166,12 @@ export const isoDateToDate = z z.date(), { decode: (isoString) => new Date(isoString), - encode: (date) => date.toISOString().slice(0, 10), + encode: (date) => { + const y = date.getFullYear(); + const m = String(date.getMonth() + 1).padStart(2, '0'); + const d = String(date.getDate()).padStart(2, '0'); + return `${y}-${m}-${d}`; + }, }, ) .meta({ example: '2024-01-01' }); @@ -251,16 +251,4 @@ export const hexColor = z .regex(hexColorRegex) .transform((val) => (val.startsWith('#') ? val : `#${val}`)); -/** - * Transform empty strings to null. Inner schema passed to this function must accept null. - * @docs https://zod.dev/api?id=preprocess - * @example emptyStringToNull(z.string().nullable()).optional() // [encouraged] final schema is optional - * @example emptyStringToNull(z.string().nullable()) // [encouraged] same as the one above, but final schema is not optional - * @example emptyStringToNull(z.string().nullish()) // [discouraged] same as the one above, might be confusing - * @example emptyStringToNull(z.string().optional()) // fails: string schema rejects null - * @example emptyStringToNull(z.string().nullable()).nullish() // [discouraged] passes, null is duplicated. use the first example instead - */ -export const emptyStringToNull = (schema: T) => - z.preprocess((val) => (val === '' ? null : val), schema); - export const sanitizeFilename = z.string().transform((val) => sanitize(val.replaceAll('.', ''))); diff --git a/server/test/fixtures/auth.stub.ts b/server/test/fixtures/auth.stub.ts index 85d52f14a1..88a2278898 100644 --- a/server/test/fixtures/auth.stub.ts +++ b/server/test/fixtures/auth.stub.ts @@ -41,6 +41,13 @@ export const authStub = { id: 'token-id', } as AuthSession, }), + adminWithElevatedPermission: Object.freeze({ + user: authUser.admin, + session: { + id: 'token-id-elevated', + hasElevatedPermission: true, + } as AuthSession, + }), adminSharedLink: Object.freeze({ user: authUser.admin, sharedLink: { diff --git a/server/test/fixtures/media.stub.ts b/server/test/fixtures/media.stub.ts index 120fe07664..7edd1c2dee 100644 --- a/server/test/fixtures/media.stub.ts +++ b/server/test/fixtures/media.stub.ts @@ -1,5 +1,13 @@ -import { NotNull } from 'kysely'; -import { ColorMatrix, ColorPrimaries, ColorTransfer, DvProfile, DvSignalCompatibility } from 'src/enum'; +import { + AacProfile, + ColorMatrix, + ColorPrimaries, + ColorTransfer, + DvProfile, + DvSignalCompatibility, + H264Profile, + HevcProfile, +} from 'src/enum'; import { AudioStreamInfo, VideoFormat, VideoInfo, VideoStreamInfo } from 'src/types'; const probeStubDefaultFormat: VideoFormat = { @@ -22,11 +30,17 @@ const probeStubDefaultVideoStream: VideoStreamInfo[] = [ colorTransfer: ColorTransfer.Bt709, colorMatrix: ColorMatrix.Bt709, pixelFormat: 'yuv420p', + frameRate: 60, timeBase: 600, + profile: HevcProfile.Main, + level: null, + dvBlSignalCompatibilityId: null, + dvLevel: null, + dvProfile: null, }, ]; -const probeStubDefaultAudioStream: AudioStreamInfo[] = [{ index: 3, codecName: 'mp3', bitrate: 100 }]; +const probeStubDefaultAudioStream: AudioStreamInfo[] = [{ index: 3, codecName: 'mp3', bitrate: 100, profile: null }]; const probeStubDefault: VideoInfo = { format: probeStubDefaultFormat, @@ -53,7 +67,13 @@ export const videoInfoStub = { colorTransfer: ColorTransfer.Bt709, colorMatrix: ColorMatrix.Bt709, pixelFormat: 'yuv420p', + frameRate: 60, timeBase: 600, + profile: HevcProfile.Main, + level: null, + dvBlSignalCompatibilityId: null, + dvLevel: null, + dvProfile: null, }, { index: 0, @@ -67,7 +87,13 @@ export const videoInfoStub = { colorTransfer: ColorTransfer.Bt709, colorMatrix: ColorMatrix.Bt709, pixelFormat: 'yuv420p', + frameRate: 60, timeBase: 600, + profile: HevcProfile.Main, + level: null, + dvBlSignalCompatibilityId: null, + dvLevel: null, + dvProfile: null, }, { index: 2, @@ -81,16 +107,22 @@ export const videoInfoStub = { colorTransfer: ColorTransfer.Bt709, colorMatrix: ColorMatrix.Bt709, pixelFormat: 'yuv420p', + frameRate: 60, timeBase: 600, + profile: HevcProfile.Main, + level: null, + dvBlSignalCompatibilityId: null, + dvLevel: null, + dvProfile: null, }, ], }), multipleAudioStreams: Object.freeze({ ...probeStubDefault, audioStreams: [ - { index: 2, codecName: 'mp3', bitrate: 102 }, - { index: 1, codecName: 'mp3', bitrate: 101 }, - { index: 0, codecName: 'mp3', bitrate: 100 }, + { index: 2, codecName: 'mp3', bitrate: 102, profile: null }, + { index: 1, codecName: 'mp3', bitrate: 101, profile: null }, + { index: 0, codecName: 'mp3', bitrate: 100, profile: null }, ], }), noHeight: Object.freeze({ @@ -108,7 +140,13 @@ export const videoInfoStub = { colorTransfer: ColorTransfer.Bt709, colorMatrix: ColorMatrix.Bt709, pixelFormat: 'yuv420p', + frameRate: 60, timeBase: 600, + profile: HevcProfile.Main, + level: null, + dvBlSignalCompatibilityId: null, + dvLevel: null, + dvProfile: null, }, ], }), @@ -127,7 +165,13 @@ export const videoInfoStub = { colorTransfer: ColorTransfer.Bt709, colorMatrix: ColorMatrix.Bt709, pixelFormat: 'yuv420p', + frameRate: 60, timeBase: 600, + profile: HevcProfile.Main, + level: null, + dvBlSignalCompatibilityId: null, + dvLevel: null, + dvProfile: null, }, ], }), @@ -159,7 +203,13 @@ export const videoInfoStub = { colorTransfer: ColorTransfer.Smpte2084, bitrate: 0, pixelFormat: 'yuv420p10le', + frameRate: 60, timeBase: 600, + profile: H264Profile.High10, + level: null, + dvBlSignalCompatibilityId: null, + dvLevel: null, + dvProfile: null, }, ], }), @@ -178,7 +228,13 @@ export const videoInfoStub = { colorTransfer: ColorTransfer.Bt709, colorMatrix: ColorMatrix.Bt709, pixelFormat: 'yuv420p10le', + frameRate: 60, timeBase: 600, + profile: H264Profile.High10, + level: null, + dvBlSignalCompatibilityId: null, + dvLevel: null, + dvProfile: null, }, ], }), @@ -197,7 +253,13 @@ export const videoInfoStub = { colorTransfer: ColorTransfer.Bt709, colorMatrix: ColorMatrix.Bt709, pixelFormat: 'yuv420p10le', + frameRate: 60, timeBase: 600, + profile: H264Profile.High10, + level: null, + dvBlSignalCompatibilityId: null, + dvLevel: null, + dvProfile: null, }, ], }), @@ -216,7 +278,13 @@ export const videoInfoStub = { colorTransfer: ColorTransfer.Bt709, colorMatrix: ColorMatrix.Bt709, pixelFormat: 'yuv420p', + frameRate: 60, timeBase: 600, + profile: H264Profile.High, + level: null, + dvBlSignalCompatibilityId: null, + dvLevel: null, + dvProfile: null, }, ], }), @@ -235,7 +303,13 @@ export const videoInfoStub = { colorTransfer: ColorTransfer.Bt709, colorMatrix: ColorMatrix.Bt709, pixelFormat: 'yuv420p', + frameRate: 60, timeBase: 600, + profile: H264Profile.Main, + level: null, + dvBlSignalCompatibilityId: null, + dvLevel: null, + dvProfile: null, }, ], }), @@ -254,27 +328,33 @@ export const videoInfoStub = { colorTransfer: ColorTransfer.Bt709, colorMatrix: ColorMatrix.Bt709, pixelFormat: 'yuv420p', + frameRate: 60, timeBase: 600, + profile: H264Profile.Main, + level: null, + dvBlSignalCompatibilityId: null, + dvLevel: null, + dvProfile: null, }, ], }), audioStreamAac: Object.freeze({ ...probeStubDefault, - audioStreams: [{ index: 1, codecName: 'aac', bitrate: 100 }], + audioStreams: [{ index: 1, codecName: 'aac', bitrate: 100, profile: AacProfile.Lc }], }), audioStreamMp3: Object.freeze({ ...probeStubDefault, - audioStreams: [{ index: 1, codecName: 'mp3', bitrate: 100 }], + audioStreams: [{ index: 1, codecName: 'mp3', bitrate: 100, profile: null }], }), audioStreamOpus: Object.freeze({ ...probeStubDefault, - audioStreams: [{ index: 1, codecName: 'opus', bitrate: 100 }], + audioStreams: [{ index: 1, codecName: 'opus', bitrate: 100, profile: null }], }), audioStreamUnknown: Object.freeze({ ...probeStubDefault, audioStreams: [ - { index: 0, codecName: 'aac', bitrate: 100 }, - { index: 1, codecName: 'unknown', bitrate: 200 }, + { index: 0, codecName: 'aac', bitrate: 100, profile: AacProfile.Lc }, + { index: 1, codecName: 'unknown', bitrate: 200, profile: null }, ], }), matroskaContainer: Object.freeze({ @@ -340,6 +420,9 @@ export const videoInfoStub = { colorMatrix: ColorMatrix.Bt2020Nc, colorTransfer: ColorTransfer.Smpte2084, timeBase: 600, + dvBlSignalCompatibilityId: null, + dvLevel: null, + dvProfile: null, }, ], }), @@ -393,7 +476,7 @@ export const videoInfoStub = { }; interface SelectedStreams { - videoStream: VideoStreamInfo & { timeBase: NotNull }; + videoStream: VideoStreamInfo & { timeBase: number }; audioStream: AudioStreamInfo | null; format: VideoFormat; } @@ -407,3 +490,128 @@ const toSelectedStreams = (info: VideoInfo) => ({ export const probeStub = Object.fromEntries( Object.entries(videoInfoStub).map(([key, info]) => [key, toSelectedStreams(info)]), ) as Record; + +export const eiffelTower = { + originalPath: 'eiffel-tower.mp4', + videoStream: { + index: 0, + width: 1080, + height: 1920, + rotation: 0, + codecName: 'h264', + profile: H264Profile.High, + level: 40, + frameCount: 557, + frameRate: 24.908_004_845_459_07, + timeBase: 90_000, + bitrate: 5_128_622, + pixelFormat: 'yuv420p', + colorPrimaries: ColorPrimaries.Smpte170M, + colorTransfer: ColorTransfer.Smpte170M, + colorMatrix: ColorMatrix.Smpte170M, + dvProfile: null, + dvLevel: null, + dvBlSignalCompatibilityId: null, + }, + audioStream: { codecName: 'aac', bitrate: 125_629, index: 1, profile: AacProfile.Lc }, + packets: { + totalDuration: 2_012_441, + packetCount: 557, + outputFrames: 557, + keyframePts: [0, 462_502, 925_004, 1_210_454, 1_387_506, 1_542_878, 1_850_008], + keyframeAccDuration: [3613, 466_077, 928_541, 1_213_968, 1_391_005, 1_546_364, 1_853_469], + keyframeOwnDuration: [3613, 3613, 3613, 3613, 3613, 3613, 3613], + }, + format: { + formatName: 'mov,mp4,m4a,3gp,3g2,mj2', + formatLongName: 'QuickTime / MOV', + duration: 22_616, + bitrate: 5_128_622, + }, +}; + +export const waterfall = { + originalPath: 'waterfall.mp4', + videoStream: { + index: 2, + width: 3840, + height: 2160, + rotation: -90, + codecName: 'hevc', + profile: HevcProfile.Main, + level: 156, + frameCount: 309, + frameRate: 29.829_901_982_867_92, + timeBase: 90_000, + bitrate: 43_363_499, + pixelFormat: 'yuvj420p', + colorPrimaries: ColorPrimaries.Bt709, + colorTransfer: ColorTransfer.Bt709, + colorMatrix: ColorMatrix.Bt709, + dvProfile: null, + dvLevel: null, + dvBlSignalCompatibilityId: null, + }, + audioStream: { codecName: 'aac', bitrate: 191_878, index: 1, profile: null }, + packets: { + totalDuration: 932_286, + packetCount: 309, + outputFrames: 309, + keyframePts: [0, 89_987, 179_974, 269_961, 359_948, 449_936, 539_923, 629_910, 725_166, 815_273, 905_295], + keyframeAccDuration: [ + 2999, 92_987, 182_974, 272_961, 362_948, 452_934, 542_922, 632_909, 728_175, 818_274, 908_296, + ], + keyframeOwnDuration: [2999, 3000, 3000, 3000, 3000, 2998, 2999, 2999, 3009, 3001, 3001], + }, + format: { + formatName: 'mov,mp4,m4a,3gp,3g2,mj2', + formatLongName: 'QuickTime / MOV', + duration: 10_359, + bitrate: 43_363_499, + }, +}; + +export const train = { + originalPath: 'train.mov', + videoStream: { + index: 0, + width: 1920, + height: 1080, + rotation: -90, + codecName: 'hevc', + profile: HevcProfile.Main10, + level: 123, + frameCount: 1229, + frameRate: 56.536_072_989_342_94, + timeBase: 600, + bitrate: 12_595_191, + pixelFormat: 'yuv420p10le', + colorPrimaries: ColorPrimaries.Bt2020, + colorTransfer: ColorTransfer.AribStdB67, + colorMatrix: ColorMatrix.Bt2020Nc, + dvProfile: DvProfile.Dvhe08, + dvLevel: 5, + dvBlSignalCompatibilityId: DvSignalCompatibility.Hlg, + }, + audioStream: { codecName: 'aac', bitrate: 175_477, index: 1, profile: AacProfile.Lc }, + packets: { + totalDuration: 12_290, + packetCount: 1229, + outputFrames: 1304, + keyframePts: [ + 0, 601, 1201, 1802, 2402, 3003, 3604, 4204, 4805, 5405, 6006, 6607, 7207, 7808, 8408, 9009, 9609, 10_210, 10_811, + 11_411, 12_062, 12_703, + ], + keyframeAccDuration: [ + 10, 580, 1180, 1780, 2380, 2980, 3580, 4180, 4780, 5380, 5980, 6580, 7180, 7780, 8380, 8980, 9580, 10_180, 10_780, + 11_380, 11_780, 12_100, + ], + keyframeOwnDuration: [10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10], + }, + format: { + formatName: 'mov,mp4,m4a,3gp,3g2,mj2', + formatLongName: 'QuickTime / MOV', + duration: 21_738, + bitrate: 12_595_191, + }, +}; diff --git a/server/test/mappers.ts b/server/test/mappers.ts index 53d1d00a13..6ee7e52ac6 100644 --- a/server/test/mappers.ts +++ b/server/test/mappers.ts @@ -1,4 +1,4 @@ -import { NotNull, Selectable, ShallowDehydrateObject } from 'kysely'; +import { Selectable, ShallowDehydrateObject } from 'kysely'; import { MapAsset } from 'src/dtos/asset-response.dto'; import { AssetEditActionItem } from 'src/dtos/editing.dto'; import { ActivityTable } from 'src/schema/tables/activity.table'; @@ -156,7 +156,7 @@ export const getForGenerateThumbnail = (asset: ReturnType files: asset.files.map((file) => getDehydrated(file)), exifInfo: getDehydrated(asset.exifInfo), edits: asset.edits.map(({ action, parameters }) => ({ action, parameters })) as AssetEditActionItem[], - videoStream: null as (VideoStreamInfo & { timeBase: NotNull }) | null, + videoStream: null as (VideoStreamInfo & { timeBase: number }) | null, audioStream: null as AudioStreamInfo | null, format: null as VideoFormat | null, }); diff --git a/server/test/medium.factory.ts b/server/test/medium.factory.ts index 8e3372011a..e7915d3f1c 100644 --- a/server/test/medium.factory.ts +++ b/server/test/medium.factory.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-unsafe-function-type */ import { Insertable, Kysely } from 'kysely'; import { DateTime } from 'luxon'; import { createHash, randomBytes } from 'node:crypto'; @@ -75,7 +74,7 @@ import { UserTable } from 'src/schema/tables/user.table'; import { BASE_SERVICE_DEPENDENCIES, BaseService } from 'src/services/base.service'; import { MetadataService } from 'src/services/metadata.service'; import { SyncService } from 'src/services/sync.service'; -import { UploadFile } from 'src/types'; +import { ClassConstructor, UploadFile } from 'src/types'; import { mockEnvData } from 'test/repositories/config.repository.mock'; import { newTelemetryRepositoryMock } from 'test/repositories/telemetry.repository.mock'; import { factory, newDate, newEmbedding, newUuid } from 'test/small.factory'; @@ -85,10 +84,6 @@ import { Mocked } from 'vitest'; // eslint-disable-next-line unicorn/prefer-module export const testAssetsDir = resolve(__dirname, '../../e2e/test-assets'); -interface ClassConstructor extends Function { - new (...args: any[]): T; -} - type MediumTestOptions = { mock: ClassConstructor[]; real: ClassConstructor[]; @@ -425,7 +420,6 @@ const newRealRepository = (key: ClassConstructor, db: Kysely): T => { case OcrRepository: case PartnerRepository: case PersonRepository: - case PluginRepository: case SearchRepository: case SessionRepository: case SharedLinkRepository: @@ -458,6 +452,10 @@ const newRealRepository = (key: ClassConstructor, db: Kysely): T => { return new key(LoggingRepository.create()); } + case PluginRepository: { + return new key(db, LoggingRepository.create()); + } + case StorageRepository: { return new key(LoggingRepository.create()); } @@ -489,7 +487,6 @@ const newMockRepository = (key: ClassConstructor) => { case OcrRepository: case PartnerRepository: case PersonRepository: - case PluginRepository: case SessionRepository: case SyncRepository: case SyncCheckpointRepository: diff --git a/server/test/medium/specs/exif/audio-video.spec.ts b/server/test/medium/specs/exif/audio-video.spec.ts index 430e7826f9..2f6af6594d 100644 --- a/server/test/medium/specs/exif/audio-video.spec.ts +++ b/server/test/medium/specs/exif/audio-video.spec.ts @@ -1,17 +1,9 @@ import { Kysely } from 'kysely'; import { resolve } from 'node:path'; -import { - AacProfile, - AssetType, - ColorMatrix, - ColorPrimaries, - ColorTransfer, - DvProfile, - DvSignalCompatibility, - H264Profile, - HevcProfile, -} from 'src/enum'; +import { AssetType } from 'src/enum'; import { DB } from 'src/schema'; +import { withAudioStream, withVideoFormat, withVideoPackets, withVideoStream } from 'src/utils/database'; +import { eiffelTower, train, waterfall } from 'test/fixtures/media.stub'; import { ExifTestContext, testAssetsDir } from 'test/medium.factory'; import { getKyselyDB } from 'test/utils'; @@ -21,122 +13,30 @@ beforeAll(async () => { database = await getKyselyDB(); }); -const fixtures = [ - { - file: 'eiffel-tower.mp4', - video: { - codecName: 'h264', - formatName: 'mov,mp4,m4a,3gp,3g2,mj2', - formatLongName: 'QuickTime / MOV', - pixelFormat: 'yuv420p', - bitrate: 5_128_622, - frameCount: 557, - timeBase: 90_000, - index: 0, - profile: H264Profile.High, - level: 40, - colorPrimaries: ColorPrimaries.Smpte170M, - colorTransfer: ColorTransfer.Smpte170M, - colorMatrix: ColorMatrix.Smpte170M, - dvProfile: null, - dvLevel: null, - dvBlSignalCompatibilityId: null, - }, - audio: { codecName: 'aac', bitrate: 125_629, index: 1, profile: AacProfile.Lc }, - keyframes: { - totalDuration: 2_012_441, - packetCount: 557, - outputFrames: 557, - pts: [0, 462_502, 925_004, 1_210_454, 1_387_506, 1_542_878, 1_850_008], - accDuration: [3613, 466_077, 928_541, 1_213_968, 1_391_005, 1_546_364, 1_853_469], - ownDuration: [3613, 3613, 3613, 3613, 3613, 3613, 3613], - }, - }, - { - file: 'waterfall.mp4', - video: { - codecName: 'hevc', - formatName: 'mov,mp4,m4a,3gp,3g2,mj2', - formatLongName: 'QuickTime / MOV', - pixelFormat: 'yuvj420p', - bitrate: 43_363_499, - frameCount: 309, - timeBase: 90_000, - index: 2, - profile: HevcProfile.Main, - level: 156, - colorPrimaries: ColorPrimaries.Bt709, - colorTransfer: ColorTransfer.Bt709, - colorMatrix: ColorMatrix.Bt709, - dvProfile: null, - dvLevel: null, - dvBlSignalCompatibilityId: null, - }, - audio: { codecName: 'aac', bitrate: 191_878, index: 1, profile: null }, - keyframes: { - totalDuration: 932_286, - packetCount: 309, - outputFrames: 309, - pts: [0, 89_987, 179_974, 269_961, 359_948, 449_936, 539_923, 629_910, 725_166, 815_273, 905_295], - accDuration: [2999, 92_987, 182_974, 272_961, 362_948, 452_934, 542_922, 632_909, 728_175, 818_274, 908_296], - ownDuration: [2999, 3000, 3000, 3000, 3000, 2998, 2999, 2999, 3009, 3001, 3001], - }, - }, - { - file: 'train.mov', - video: { - codecName: 'hevc', - formatName: 'mov,mp4,m4a,3gp,3g2,mj2', - formatLongName: 'QuickTime / MOV', - pixelFormat: 'yuv420p10le', - bitrate: 12_595_191, - frameCount: 1229, - timeBase: 600, - index: 0, - profile: HevcProfile.Main10, - level: 123, - colorPrimaries: ColorPrimaries.Bt2020, - colorTransfer: ColorTransfer.AribStdB67, - colorMatrix: ColorMatrix.Bt2020Nc, - dvProfile: DvProfile.Dvhe08, - dvLevel: 5, - dvBlSignalCompatibilityId: DvSignalCompatibility.Hlg, - }, - audio: { codecName: 'aac', bitrate: 175_477, index: 1, profile: AacProfile.Lc }, - keyframes: { - totalDuration: 12_290, - packetCount: 1229, - outputFrames: 1303, - pts: [ - 0, 601, 1201, 1802, 2402, 3003, 3604, 4204, 4805, 5405, 6006, 6607, 7207, 7808, 8408, 9009, 9609, 10_210, - 10_811, 11_411, 12_062, 12_703, - ], - accDuration: [ - 10, 580, 1180, 1780, 2380, 2980, 3580, 4180, 4780, 5380, 5980, 6580, 7180, 7780, 8380, 8980, 9580, 10_180, - 10_780, 11_380, 11_780, 12_100, - ], - ownDuration: [10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10], - }, - }, -]; - -const isExpected = (name: T, id: string, expected: Omit) => { - const { table, ref } = database.dynamic; - const res = database.selectFrom(table(name).as('t')).selectAll().where(ref('assetId'), '=', id).executeTakeFirst(); - return expect(res).resolves.toEqual({ ...expected, assetId: id }); -}; +const fixtures = [eiffelTower, waterfall, train]; describe('video metadata extraction', () => { - it.each(fixtures)('$file', async ({ file, video, audio, keyframes }) => { + it.each(fixtures)('$originalPath', async ({ originalPath: path, videoStream, audioStream, packets, format }) => { const ctx = new ExifTestContext(database); const { user } = await ctx.newUser(); - const originalPath = resolve(testAssetsDir, 'videos', file); + const originalPath = resolve(testAssetsDir, 'videos', path); const { asset } = await ctx.newAsset({ ownerId: user.id, originalPath, type: AssetType.Video }); await ctx.sut.handleMetadataExtraction({ id: asset.id }); - await isExpected('asset_audio', asset.id, audio); - await isExpected('asset_video', asset.id, video); - await isExpected('asset_keyframe', asset.id, keyframes); + const result = await database + .selectFrom('asset') + .innerJoin('asset_exif', 'asset.id', 'asset_exif.assetId') + .innerJoin('asset_video', 'asset.id', 'asset_video.assetId') + .innerJoin('asset_keyframe', 'asset.id', 'asset_keyframe.assetId') + .leftJoin('asset_audio', 'asset.id', 'asset_audio.assetId') + .where('asset.id', '=', asset.id) + .select((eb) => withVideoStream(eb).$notNull().as('videoStream')) + .select((eb) => withAudioStream(eb).as('audioStream')) + .select((eb) => withVideoPackets(eb).$notNull().as('packets')) + .select((eb) => withVideoFormat(eb).$notNull().as('format')) + .executeTakeFirst(); + + expect(result).toEqual({ videoStream, audioStream, packets, format }); }); }); diff --git a/server/test/medium/specs/services/asset-media.service.spec.ts b/server/test/medium/specs/services/asset-media.service.spec.ts index 1cfe8e9f84..e9395bfebf 100644 --- a/server/test/medium/specs/services/asset-media.service.spec.ts +++ b/server/test/medium/specs/services/asset-media.service.spec.ts @@ -43,9 +43,11 @@ describe(AssetService.name, () => { ctx.getMock(EventRepository).emit.mockResolvedValue(); ctx.getMock(JobRepository).queue.mockResolvedValue(); + const fileSizeInByte = 12_345; + const { user } = await ctx.newUser(); const { asset } = await ctx.newAsset({ ownerId: user.id }); - await ctx.newExif({ assetId: asset.id, fileSizeInByte: 12_345 }); + await ctx.newExif({ assetId: asset.id, fileSizeInByte }); const auth = factory.auth({ user: { id: user.id } }); await expect( @@ -56,7 +58,7 @@ describe(AssetService.name, () => { fileCreatedAt: new Date(), assetData: Buffer.from('some data'), }, - mediumFactory.uploadFile(), + mediumFactory.uploadFile({ size: fileSizeInByte }), ), ).resolves.toEqual({ id: expect.any(String), @@ -65,6 +67,7 @@ describe(AssetService.name, () => { expect(ctx.getMock(EventRepository).emit).toHaveBeenCalledWith('AssetCreate', { asset: expect.objectContaining({}), + file: expect.objectContaining({ size: fileSizeInByte }), }); }); diff --git a/server/test/medium/specs/services/plugin.service.spec.ts b/server/test/medium/specs/services/plugin.service.spec.ts index b70e8e8d54..de4882a575 100644 --- a/server/test/medium/specs/services/plugin.service.spec.ts +++ b/server/test/medium/specs/services/plugin.service.spec.ts @@ -1,5 +1,5 @@ import { Kysely } from 'kysely'; -import { PluginContext } from 'src/enum'; +import { WorkflowType } from 'src/enum'; import { AccessRepository } from 'src/repositories/access.repository'; import { LoggingRepository } from 'src/repositories/logging.repository'; import { PluginRepository } from 'src/repositories/plugin.repository'; @@ -9,7 +9,9 @@ import { newMediumService } from 'test/medium.factory'; import { getKyselyDB } from 'test/utils'; let defaultDatabase: Kysely; -let pluginRepo: PluginRepository; + +const wasmBytes = Buffer.from('some-wasm-binary-data'); +const sha256hash = Buffer.from('some-manifest-hash'); const setup = (db?: Kysely) => { return newMediumService(PluginService, { @@ -21,7 +23,6 @@ const setup = (db?: Kysely) => { beforeAll(async () => { defaultDatabase = await getKyselyDB(); - pluginRepo = new PluginRepository(defaultDatabase); }); afterEach(async () => { @@ -32,214 +33,205 @@ describe(PluginService.name, () => { describe('getAll', () => { it('should return empty array when no plugins exist', async () => { const { sut } = setup(); - - const plugins = await sut.getAll(); - - expect(plugins).toEqual([]); + await expect(sut.search({})).resolves.toEqual([]); }); - it('should return plugin without filters and actions', async () => { - const { sut } = setup(); + it('should return plugin without methods', async () => { + const { ctx, sut } = setup(); - const result = await pluginRepo.loadPlugin( + const result = await ctx.get(PluginRepository).upsert( { + enabled: true, name: 'test-plugin', title: 'Test Plugin', description: 'A test plugin', author: 'Test Author', version: '1.0.0', - wasm: { path: '/path/to/test.wasm' }, + templates: [], + wasmBytes, + sha256hash, }, - '/test/base/path', + [], ); - const plugins = await sut.getAll(); + const plugins = await sut.search({}); expect(plugins).toHaveLength(1); expect(plugins[0]).toMatchObject({ - id: result.plugin.id, + id: result.id, name: 'test-plugin', description: 'A test plugin', author: 'Test Author', version: '1.0.0', - filters: [], - actions: [], + methods: [], }); }); - it('should return plugin with filters and actions', async () => { - const { sut } = setup(); + it('should return plugin with multiple methods', async () => { + const { ctx, sut } = setup(); - const result = await pluginRepo.loadPlugin( + await ctx.get(PluginRepository).upsert( { + enabled: true, name: 'full-plugin', title: 'Full Plugin', - description: 'A plugin with filters and actions', + description: 'A plugin with multiple methods', author: 'Test Author', version: '1.0.0', - wasm: { path: '/path/to/full.wasm' }, - filters: [ - { - methodName: 'test-filter', - title: 'Test Filter', - description: 'A test filter', - supportedContexts: [PluginContext.Asset], - schema: { type: 'object', properties: {} }, - }, - ], - actions: [ - { - methodName: 'test-action', - title: 'Test Action', - description: 'A test action', - supportedContexts: [PluginContext.Asset], - schema: { type: 'object', properties: {} }, - }, - ], + templates: [], + wasmBytes, + sha256hash, }, - '/test/base/path', + [ + { + name: 'test-filter', + title: 'Test Filter', + description: 'A test filter', + types: [WorkflowType.AssetV1], + schema: { type: 'object', properties: {} }, + }, + { + name: 'test-action', + title: 'Test Action', + description: 'A test action', + types: [WorkflowType.AssetV1], + schema: { type: 'object', properties: {} }, + }, + ], ); - const plugins = await sut.getAll(); + const plugins = await sut.search({}); expect(plugins).toHaveLength(1); expect(plugins[0]).toMatchObject({ - id: result.plugin.id, name: 'full-plugin', - filters: [ + methods: [ { - id: result.filters[0].id, - pluginId: result.plugin.id, - methodName: 'test-filter', + name: 'test-filter', title: 'Test Filter', description: 'A test filter', - supportedContexts: [PluginContext.Asset], + types: [WorkflowType.AssetV1], schema: { type: 'object', properties: {} }, }, - ], - actions: [ { - id: result.actions[0].id, - pluginId: result.plugin.id, - methodName: 'test-action', + name: 'test-action', title: 'Test Action', description: 'A test action', - supportedContexts: [PluginContext.Asset], + types: [WorkflowType.AssetV1], schema: { type: 'object', properties: {} }, }, ], }); }); - it('should return multiple plugins with their respective filters and actions', async () => { - const { sut } = setup(); + it('should return multiple plugins with their respective methods', async () => { + const { ctx, sut } = setup(); - await pluginRepo.loadPlugin( + await ctx.get(PluginRepository).upsert( { + enabled: true, name: 'plugin-1', title: 'Plugin 1', description: 'First plugin', author: 'Author 1', version: '1.0.0', - wasm: { path: '/path/to/plugin1.wasm' }, - filters: [ - { - methodName: 'filter-1', - title: 'Filter 1', - description: 'Filter for plugin 1', - supportedContexts: [PluginContext.Asset], - schema: undefined, - }, - ], + templates: [], + wasmBytes, + sha256hash, }, - '/test/base/path', + [ + { + name: 'filter-1', + title: 'Filter 1', + description: 'Filter for plugin 1', + types: [WorkflowType.AssetV1], + }, + ], ); - await pluginRepo.loadPlugin( + await ctx.get(PluginRepository).upsert( { + enabled: true, name: 'plugin-2', title: 'Plugin 2', description: 'Second plugin', author: 'Author 2', version: '2.0.0', - wasm: { path: '/path/to/plugin2.wasm' }, - actions: [ - { - methodName: 'action-2', - title: 'Action 2', - description: 'Action for plugin 2', - supportedContexts: [PluginContext.Album], - schema: undefined, - }, - ], + templates: [], + wasmBytes, + sha256hash, }, - '/test/base/path', + [ + { + name: 'action-2', + title: 'Action 2', + description: 'Action for plugin 2', + types: [WorkflowType.AssetV1], + }, + ], ); - const plugins = await sut.getAll(); + const plugins = await sut.search({}); expect(plugins).toHaveLength(2); expect(plugins[0].name).toBe('plugin-1'); - expect(plugins[0].filters).toHaveLength(1); - expect(plugins[0].actions).toHaveLength(0); + expect(plugins[0].methods).toHaveLength(1); expect(plugins[1].name).toBe('plugin-2'); - expect(plugins[1].filters).toHaveLength(0); - expect(plugins[1].actions).toHaveLength(1); + expect(plugins[1].methods).toHaveLength(1); }); - it('should handle plugin with multiple filters and actions', async () => { - const { sut } = setup(); + it('should handle plugin with multiple methods', async () => { + const { ctx, sut } = setup(); - await pluginRepo.loadPlugin( + await ctx.get(PluginRepository).upsert( { + enabled: true, name: 'multi-plugin', title: 'Multi Plugin', - description: 'Plugin with multiple items', + description: 'Plugin with multiple methods', author: 'Test Author', version: '1.0.0', - wasm: { path: '/path/to/multi.wasm' }, - filters: [ - { - methodName: 'filter-a', - title: 'Filter A', - description: 'First filter', - supportedContexts: [PluginContext.Asset], - schema: undefined, - }, - { - methodName: 'filter-b', - title: 'Filter B', - description: 'Second filter', - supportedContexts: [PluginContext.Album], - schema: undefined, - }, - ], - actions: [ - { - methodName: 'action-x', - title: 'Action X', - description: 'First action', - supportedContexts: [PluginContext.Asset], - schema: undefined, - }, - { - methodName: 'action-y', - title: 'Action Y', - description: 'Second action', - supportedContexts: [PluginContext.Person], - schema: undefined, - }, - ], + templates: [], + wasmBytes, + sha256hash, }, - '/test/base/path', + [ + { + name: 'filter-a', + title: 'Filter A', + description: 'First filter', + types: [WorkflowType.AssetV1], + schema: undefined, + }, + { + name: 'filter-b', + title: 'Filter B', + description: 'Second filter', + types: [WorkflowType.AssetV1], + schema: undefined, + }, + { + name: 'action-x', + title: 'Action X', + description: 'First action', + types: [WorkflowType.AssetV1], + schema: undefined, + }, + { + name: 'action-y', + title: 'Action Y', + description: 'Second action', + types: [WorkflowType.AssetV1], + schema: undefined, + }, + ], ); - const plugins = await sut.getAll(); + const plugins = await sut.search({}); expect(plugins).toHaveLength(1); - expect(plugins[0].filters).toHaveLength(2); - expect(plugins[0].actions).toHaveLength(2); + expect(plugins[0].methods).toHaveLength(4); }); }); @@ -250,55 +242,51 @@ describe(PluginService.name, () => { await expect(sut.get('00000000-0000-0000-0000-000000000000')).rejects.toThrow('Plugin not found'); }); - it('should return single plugin with filters and actions', async () => { - const { sut } = setup(); + it('should return single plugin with methods', async () => { + const { ctx, sut } = setup(); - const result = await pluginRepo.loadPlugin( + const result = await ctx.get(PluginRepository).upsert( { + enabled: true, name: 'single-plugin', title: 'Single Plugin', description: 'A single plugin', author: 'Test Author', version: '1.0.0', - wasm: { path: '/path/to/single.wasm' }, - filters: [ - { - methodName: 'single-filter', - title: 'Single Filter', - description: 'A single filter', - supportedContexts: [PluginContext.Asset], - schema: undefined, - }, - ], - actions: [ - { - methodName: 'single-action', - title: 'Single Action', - description: 'A single action', - supportedContexts: [PluginContext.Asset], - schema: undefined, - }, - ], + templates: [], + sha256hash, + wasmBytes, }, - '/test/base/path', - ); - - const pluginResult = await sut.get(result.plugin.id); - - expect(pluginResult).toMatchObject({ - id: result.plugin.id, - name: 'single-plugin', - filters: [ + [ { - id: result.filters[0].id, - methodName: 'single-filter', + name: 'single-filter', title: 'Single Filter', + description: 'A single filter', + types: [WorkflowType.AssetV1], + schema: undefined, + }, + { + name: 'single-action', + title: 'Single Action', + description: 'A single action', + types: [WorkflowType.AssetV1], + schema: undefined, }, ], - actions: [ + ); + + const pluginResult = await sut.get(result.id); + + expect(pluginResult).toMatchObject({ + id: result.id, + name: 'single-plugin', + methods: [ { - id: result.actions[0].id, - methodName: 'single-action', + name: 'single-filter', + title: 'Single Filter', + }, + { + name: 'single-action', title: 'Single Action', }, ], diff --git a/server/test/medium/specs/services/timeline.service.spec.ts b/server/test/medium/specs/services/timeline.service.spec.ts index eaca4dcc14..d7013b84be 100644 --- a/server/test/medium/specs/services/timeline.service.spec.ts +++ b/server/test/medium/specs/services/timeline.service.spec.ts @@ -1,10 +1,11 @@ import { BadRequestException } from '@nestjs/common'; import { Kysely } from 'kysely'; -import { AssetVisibility } from 'src/enum'; +import { AssetVisibility, SharedLinkType } from 'src/enum'; import { AccessRepository } from 'src/repositories/access.repository'; import { AssetRepository } from 'src/repositories/asset.repository'; import { LoggingRepository } from 'src/repositories/logging.repository'; import { PartnerRepository } from 'src/repositories/partner.repository'; +import { SharedLinkRepository } from 'src/repositories/shared-link.repository'; import { DB } from 'src/schema'; import { TimelineService } from 'src/services/timeline.service'; import { newMediumService } from 'test/medium.factory'; @@ -50,13 +51,13 @@ describe(TimelineService.name, () => { const response1 = sut.getTimeBuckets(auth, { withPartners: true, visibility: AssetVisibility.Archive }); await expect(response1).rejects.toBeInstanceOf(BadRequestException); await expect(response1).rejects.toThrow( - 'withPartners is only supported for non-archived, non-trashed, non-favorited assets', + 'withPartners is only supported for non-archived, non-trashed, non-favorited, non-locked assets', ); const response2 = sut.getTimeBuckets(auth, { withPartners: true }); await expect(response2).rejects.toBeInstanceOf(BadRequestException); await expect(response2).rejects.toThrow( - 'withPartners is only supported for non-archived, non-trashed, non-favorited assets', + 'withPartners is only supported for non-archived, non-trashed, non-favorited, non-locked assets', ); }); @@ -66,13 +67,13 @@ describe(TimelineService.name, () => { const response1 = sut.getTimeBuckets(auth, { withPartners: true, isFavorite: false }); await expect(response1).rejects.toBeInstanceOf(BadRequestException); await expect(response1).rejects.toThrow( - 'withPartners is only supported for non-archived, non-trashed, non-favorited assets', + 'withPartners is only supported for non-archived, non-trashed, non-favorited, non-locked assets', ); const response2 = sut.getTimeBuckets(auth, { withPartners: true, isFavorite: true }); await expect(response2).rejects.toBeInstanceOf(BadRequestException); await expect(response2).rejects.toThrow( - 'withPartners is only supported for non-archived, non-trashed, non-favorited assets', + 'withPartners is only supported for non-archived, non-trashed, non-favorited, non-locked assets', ); }); @@ -82,7 +83,7 @@ describe(TimelineService.name, () => { const response = sut.getTimeBuckets(auth, { withPartners: true, isTrashed: true }); await expect(response).rejects.toBeInstanceOf(BadRequestException); await expect(response).rejects.toThrow( - 'withPartners is only supported for non-archived, non-trashed, non-favorited assets', + 'withPartners is only supported for non-archived, non-trashed, non-favorited, non-locked assets', ); }); @@ -118,6 +119,7 @@ describe(TimelineService.name, () => { expect(response).toEqual({ city: [], country: [], + createdAt: [], duration: [], id: [], visibility: [], @@ -206,4 +208,32 @@ describe(TimelineService.name, () => { expect(response2).toEqual(expect.objectContaining({ id: [asset2.id, asset1.id], isFavorite: [true, false] })); }); }); + + it('should strip geodata metadata if shared link without exif', async () => { + const { sut, ctx } = setup(); + const sharedLinkRepo = ctx.get(SharedLinkRepository); + + const { user } = await ctx.newUser(); + const { asset } = await ctx.newAsset({ + ownerId: user.id, + localDateTime: new Date('1970-02-12'), + deletedAt: new Date(), + }); + const { album } = await ctx.newAlbum({ ownerId: user.id }); + await ctx.newAlbumAsset({ albumId: album.id, assetId: asset.id }); + + const { id: sharedLinkId } = await sharedLinkRepo.create({ + allowUpload: false, + key: Buffer.from('123'), + type: SharedLinkType.Album, + userId: user.id, + albumId: album.id, + }); + + await ctx.newExif({ assetId: asset.id, city: 'Austin', country: 'USA' }); + const auth = factory.auth({ sharedLink: { id: sharedLinkId, showExif: false } }); + const rawResponse = await sut.getTimeBucket(auth, { albumId: album.id, timeBucket: '1970-02-01', isTrashed: true }); + const response = JSON.parse(rawResponse); + expect(response).not.toEqual(expect.objectContaining({ city: expect.any(Array), country: expect.any(Array) })); + }); }); diff --git a/server/test/medium/specs/services/workflow.service.spec.ts b/server/test/medium/specs/services/workflow.service.spec.ts index 229737c531..b4b52be98c 100644 --- a/server/test/medium/specs/services/workflow.service.spec.ts +++ b/server/test/medium/specs/services/workflow.service.spec.ts @@ -1,5 +1,6 @@ +import { WorkflowTrigger } from '@immich/plugin-sdk'; import { Kysely } from 'kysely'; -import { PluginContext, PluginTriggerType } from 'src/enum'; +import { WorkflowType } from 'src/enum'; import { AccessRepository } from 'src/repositories/access.repository'; import { LoggingRepository } from 'src/repositories/logging.repository'; import { PluginRepository } from 'src/repositories/plugin.repository'; @@ -20,53 +21,51 @@ const setup = (db?: Kysely) => { }); }; +const wasmBytes = Buffer.from('random-wasm-bytes'); +const sha256hash = Buffer.from('some-manifest-hash'); + beforeAll(async () => { defaultDatabase = await getKyselyDB(); }); describe(WorkflowService.name, () => { let testPluginId: string; - let testFilterId: string; - let testActionId: string; beforeAll(async () => { - // Create a test plugin with filters and actions once for all tests - const pluginRepo = new PluginRepository(defaultDatabase); - const result = await pluginRepo.loadPlugin( + const { ctx } = setup(); + // Create a test plugin with methods and actions once for all tests + const pluginRepo = ctx.get(PluginRepository); + const result = await pluginRepo.upsert( { + enabled: true, name: 'test-core-plugin', title: 'Test Core Plugin', description: 'A test core plugin for workflow tests', author: 'Test Author', version: '1.0.0', - wasm: { - path: '/test/path.wasm', - }, - filters: [ - { - methodName: 'test-filter', - title: 'Test Filter', - description: 'A test filter', - supportedContexts: [PluginContext.Asset], - schema: undefined, - }, - ], - actions: [ - { - methodName: 'test-action', - title: 'Test Action', - description: 'A test action', - supportedContexts: [PluginContext.Asset], - schema: undefined, - }, - ], + templates: [], + wasmBytes, + sha256hash, }, - '/plugins/test-core-plugin', + [ + { + name: 'test-filter', + title: 'Test Filter', + description: 'A test filter', + types: [WorkflowType.AssetV1], + schema: undefined, + }, + { + name: 'test-action', + title: 'Test Action', + description: 'A test action', + types: [WorkflowType.AssetV1], + schema: undefined, + }, + ], ); - testPluginId = result.plugin.id; - testFilterId = result.filters[0].id; - testActionId = result.actions[0].id; + testPluginId = result.id; }); afterAll(async () => { @@ -74,230 +73,27 @@ describe(WorkflowService.name, () => { }); describe('create', () => { - it('should create a workflow without filters or actions', async () => { + it('should create a workflow', async () => { const { sut, ctx } = setup(); const { user } = await ctx.newUser(); const auth = factory.auth({ user }); const workflow = await sut.create(auth, { - triggerType: PluginTriggerType.AssetCreate, + trigger: WorkflowTrigger.AssetCreate, name: 'test-workflow', description: 'A test workflow', enabled: true, - filters: [], - actions: [], }); expect(workflow).toMatchObject({ id: expect.any(String), - ownerId: user.id, - triggerType: PluginTriggerType.AssetCreate, + trigger: WorkflowTrigger.AssetCreate, name: 'test-workflow', description: 'A test workflow', enabled: true, - filters: [], - actions: [], }); }); - - it('should create a workflow with filters and actions', async () => { - const { sut, ctx } = setup(); - const { user } = await ctx.newUser(); - const auth = factory.auth({ user }); - - const workflow = await sut.create(auth, { - triggerType: PluginTriggerType.AssetCreate, - name: 'test-workflow-with-relations', - description: 'A test workflow with filters and actions', - enabled: true, - filters: [ - { - pluginFilterId: testFilterId, - filterConfig: { key: 'value' }, - }, - ], - actions: [ - { - pluginActionId: testActionId, - actionConfig: { action: 'test' }, - }, - ], - }); - - expect(workflow).toMatchObject({ - id: expect.any(String), - ownerId: user.id, - triggerType: PluginTriggerType.AssetCreate, - name: 'test-workflow-with-relations', - enabled: true, - }); - - expect(workflow.filters).toHaveLength(1); - expect(workflow.filters[0]).toMatchObject({ - id: expect.any(String), - workflowId: workflow.id, - pluginFilterId: testFilterId, - filterConfig: { key: 'value' }, - order: 0, - }); - - expect(workflow.actions).toHaveLength(1); - expect(workflow.actions[0]).toMatchObject({ - id: expect.any(String), - workflowId: workflow.id, - pluginActionId: testActionId, - actionConfig: { action: 'test' }, - order: 0, - }); - }); - - it('should throw error when creating workflow with invalid filter', async () => { - const { sut, ctx } = setup(); - const { user } = await ctx.newUser(); - const auth = factory.auth({ user }); - - await expect( - sut.create(auth, { - triggerType: PluginTriggerType.AssetCreate, - name: 'invalid-workflow', - description: 'A workflow with invalid filter', - enabled: true, - filters: [{ pluginFilterId: factory.uuid(), filterConfig: { key: 'value' } }], - actions: [], - }), - ).rejects.toThrow('Invalid filter ID'); - }); - - it('should throw error when creating workflow with invalid action', async () => { - const { sut, ctx } = setup(); - const { user } = await ctx.newUser(); - const auth = factory.auth({ user }); - - await expect( - sut.create(auth, { - triggerType: PluginTriggerType.AssetCreate, - name: 'invalid-workflow', - description: 'A workflow with invalid action', - enabled: true, - filters: [], - actions: [{ pluginActionId: factory.uuid(), actionConfig: { action: 'test' } }], - }), - ).rejects.toThrow('Invalid action ID'); - }); - - it('should throw error when filter does not support trigger context', async () => { - const { sut, ctx } = setup(); - const { user } = await ctx.newUser(); - const auth = factory.auth({ user }); - - // Create a plugin with a filter that only supports Album context - const pluginRepo = new PluginRepository(defaultDatabase); - const result = await pluginRepo.loadPlugin( - { - name: 'album-only-plugin', - title: 'Album Only Plugin', - description: 'Plugin with album-only filter', - author: 'Test Author', - version: '1.0.0', - wasm: { path: '/test/album-plugin.wasm' }, - filters: [ - { - methodName: 'album-filter', - title: 'Album Filter', - description: 'A filter that only works with albums', - supportedContexts: [PluginContext.Album], - schema: undefined, - }, - ], - }, - '/plugins/test-core-plugin', - ); - - await expect( - sut.create(auth, { - triggerType: PluginTriggerType.AssetCreate, - name: 'invalid-context-workflow', - description: 'A workflow with context mismatch', - enabled: true, - filters: [{ pluginFilterId: result.filters[0].id }], - actions: [], - }), - ).rejects.toThrow('does not support asset context'); - }); - - it('should throw error when action does not support trigger context', async () => { - const { sut, ctx } = setup(); - const { user } = await ctx.newUser(); - const auth = factory.auth({ user }); - - // Create a plugin with an action that only supports Person context - const pluginRepo = new PluginRepository(defaultDatabase); - const result = await pluginRepo.loadPlugin( - { - name: 'person-only-plugin', - title: 'Person Only Plugin', - description: 'Plugin with person-only action', - author: 'Test Author', - version: '1.0.0', - wasm: { path: '/test/person-plugin.wasm' }, - actions: [ - { - methodName: 'person-action', - title: 'Person Action', - description: 'An action that only works with persons', - supportedContexts: [PluginContext.Person], - schema: undefined, - }, - ], - }, - '/plugins/test-core-plugin', - ); - - await expect( - sut.create(auth, { - triggerType: PluginTriggerType.AssetCreate, - name: 'invalid-context-workflow', - description: 'A workflow with context mismatch', - enabled: true, - filters: [], - actions: [{ pluginActionId: result.actions[0].id }], - }), - ).rejects.toThrow('does not support asset context'); - }); - - it('should create workflow with multiple filters and actions in correct order', async () => { - const { sut, ctx } = setup(); - const { user } = await ctx.newUser(); - const auth = factory.auth({ user }); - - const workflow = await sut.create(auth, { - triggerType: PluginTriggerType.AssetCreate, - name: 'multi-step-workflow', - description: 'A workflow with multiple filters and actions', - enabled: true, - filters: [ - { pluginFilterId: testFilterId, filterConfig: { step: 1 } }, - { pluginFilterId: testFilterId, filterConfig: { step: 2 } }, - ], - actions: [ - { pluginActionId: testActionId, actionConfig: { step: 1 } }, - { pluginActionId: testActionId, actionConfig: { step: 2 } }, - { pluginActionId: testActionId, actionConfig: { step: 3 } }, - ], - }); - - expect(workflow.filters).toHaveLength(2); - expect(workflow.filters[0].order).toBe(0); - expect(workflow.filters[0].filterConfig).toEqual({ step: 1 }); - expect(workflow.filters[1].order).toBe(1); - expect(workflow.filters[1].filterConfig).toEqual({ step: 2 }); - - expect(workflow.actions).toHaveLength(3); - expect(workflow.actions[0].order).toBe(0); - expect(workflow.actions[1].order).toBe(1); - expect(workflow.actions[2].order).toBe(2); - }); }); describe('getAll', () => { @@ -307,24 +103,20 @@ describe(WorkflowService.name, () => { const auth = factory.auth({ user }); const workflow1 = await sut.create(auth, { - triggerType: PluginTriggerType.AssetCreate, + trigger: WorkflowTrigger.AssetCreate, name: 'workflow-1', description: 'First workflow', enabled: true, - filters: [], - actions: [], }); const workflow2 = await sut.create(auth, { - triggerType: PluginTriggerType.AssetCreate, + trigger: WorkflowTrigger.AssetCreate, name: 'workflow-2', description: 'Second workflow', enabled: false, - filters: [], - actions: [], }); - const workflows = await sut.getAll(auth); + const workflows = await sut.search(auth, {}); expect(workflows).toHaveLength(2); expect(workflows).toEqual( @@ -340,7 +132,7 @@ describe(WorkflowService.name, () => { const { user } = await ctx.newUser(); const auth = factory.auth({ user }); - const workflows = await sut.getAll(auth); + const workflows = await sut.search(auth, {}); expect(workflows).toEqual([]); }); @@ -353,424 +145,15 @@ describe(WorkflowService.name, () => { const auth2 = factory.auth({ user: user2 }); await sut.create(auth1, { - triggerType: PluginTriggerType.AssetCreate, + trigger: WorkflowTrigger.AssetCreate, name: 'user1-workflow', description: 'User 1 workflow', enabled: true, - filters: [], - actions: [], }); - const user2Workflows = await sut.getAll(auth2); + const user2Workflows = await sut.search(auth2, {}); expect(user2Workflows).toEqual([]); }); }); - - describe('get', () => { - it('should return a specific workflow by id', async () => { - const { sut, ctx } = setup(); - const { user } = await ctx.newUser(); - const auth = factory.auth({ user }); - - const created = await sut.create(auth, { - triggerType: PluginTriggerType.AssetCreate, - name: 'test-workflow', - description: 'A test workflow', - enabled: true, - filters: [{ pluginFilterId: testFilterId, filterConfig: { key: 'value' } }], - actions: [{ pluginActionId: testActionId, actionConfig: { action: 'test' } }], - }); - - const workflow = await sut.get(auth, created.id); - - expect(workflow).toMatchObject({ - id: created.id, - name: 'test-workflow', - description: 'A test workflow', - enabled: true, - }); - expect(workflow.filters).toHaveLength(1); - expect(workflow.actions).toHaveLength(1); - }); - - it('should throw error when workflow does not exist', async () => { - const { sut, ctx } = setup(); - const { user } = await ctx.newUser(); - const auth = factory.auth({ user }); - - await expect(sut.get(auth, '66da82df-e424-4bf4-b6f3-5d8e71620dae')).rejects.toThrow(); - }); - - it('should throw error when user does not have access to workflow', async () => { - const { sut, ctx } = setup(); - const { user: user1 } = await ctx.newUser(); - const { user: user2 } = await ctx.newUser(); - const auth1 = factory.auth({ user: user1 }); - const auth2 = factory.auth({ user: user2 }); - - const workflow = await sut.create(auth1, { - triggerType: PluginTriggerType.AssetCreate, - name: 'private-workflow', - description: 'Private workflow', - enabled: true, - filters: [], - actions: [], - }); - - await expect(sut.get(auth2, workflow.id)).rejects.toThrow(); - }); - }); - - describe('update', () => { - it('should update workflow basic fields', async () => { - const { sut, ctx } = setup(); - const { user } = await ctx.newUser(); - const auth = factory.auth({ user }); - - const created = await sut.create(auth, { - triggerType: PluginTriggerType.AssetCreate, - name: 'original-workflow', - description: 'Original description', - enabled: true, - filters: [], - actions: [], - }); - - const updated = await sut.update(auth, created.id, { - name: 'updated-workflow', - description: 'Updated description', - enabled: false, - }); - - expect(updated).toMatchObject({ - id: created.id, - name: 'updated-workflow', - description: 'Updated description', - enabled: false, - }); - }); - - it('should update workflow filters', async () => { - const { sut, ctx } = setup(); - const { user } = await ctx.newUser(); - const auth = factory.auth({ user }); - - const created = await sut.create(auth, { - triggerType: PluginTriggerType.AssetCreate, - name: 'test-workflow', - description: 'Test', - enabled: true, - filters: [{ pluginFilterId: testFilterId, filterConfig: { old: 'config' } }], - actions: [], - }); - - const updated = await sut.update(auth, created.id, { - filters: [ - { pluginFilterId: testFilterId, filterConfig: { new: 'config' } }, - { pluginFilterId: testFilterId, filterConfig: { second: 'filter' } }, - ], - }); - - expect(updated.filters).toHaveLength(2); - expect(updated.filters[0].filterConfig).toEqual({ new: 'config' }); - expect(updated.filters[1].filterConfig).toEqual({ second: 'filter' }); - }); - - it('should update workflow actions', async () => { - const { sut, ctx } = setup(); - const { user } = await ctx.newUser(); - const auth = factory.auth({ user }); - - const created = await sut.create(auth, { - triggerType: PluginTriggerType.AssetCreate, - name: 'test-workflow', - description: 'Test', - enabled: true, - filters: [], - actions: [{ pluginActionId: testActionId, actionConfig: { old: 'config' } }], - }); - - const updated = await sut.update(auth, created.id, { - actions: [ - { pluginActionId: testActionId, actionConfig: { new: 'config' } }, - { pluginActionId: testActionId, actionConfig: { second: 'action' } }, - ], - }); - - expect(updated.actions).toHaveLength(2); - expect(updated.actions[0].actionConfig).toEqual({ new: 'config' }); - expect(updated.actions[1].actionConfig).toEqual({ second: 'action' }); - }); - - it('should clear filters when updated with empty array', async () => { - const { sut, ctx } = setup(); - const { user } = await ctx.newUser(); - const auth = factory.auth({ user }); - - const created = await sut.create(auth, { - triggerType: PluginTriggerType.AssetCreate, - name: 'test-workflow', - description: 'Test', - enabled: true, - filters: [{ pluginFilterId: testFilterId, filterConfig: { key: 'value' } }], - actions: [], - }); - - const updated = await sut.update(auth, created.id, { - filters: [], - }); - - expect(updated.filters).toHaveLength(0); - }); - - it('should throw error when no fields to update', async () => { - const { sut, ctx } = setup(); - const { user } = await ctx.newUser(); - const auth = factory.auth({ user }); - - const created = await sut.create(auth, { - triggerType: PluginTriggerType.AssetCreate, - name: 'test-workflow', - description: 'Test', - enabled: true, - filters: [], - actions: [], - }); - - await expect(sut.update(auth, created.id, {})).rejects.toThrow('No fields to update'); - }); - - it('should throw error when updating non-existent workflow', async () => { - const { sut, ctx } = setup(); - const { user } = await ctx.newUser(); - const auth = factory.auth({ user }); - - await expect(sut.update(auth, factory.uuid(), { name: 'updated-name' })).rejects.toThrow(); - }); - - it('should throw error when user does not have access to update workflow', async () => { - const { sut, ctx } = setup(); - const { user: user1 } = await ctx.newUser(); - const { user: user2 } = await ctx.newUser(); - const auth1 = factory.auth({ user: user1 }); - const auth2 = factory.auth({ user: user2 }); - - const workflow = await sut.create(auth1, { - triggerType: PluginTriggerType.AssetCreate, - name: 'private-workflow', - description: 'Private', - enabled: true, - filters: [], - actions: [], - }); - - await expect( - sut.update(auth2, workflow.id, { - name: 'hacked-workflow', - }), - ).rejects.toThrow(); - }); - - it('should throw error when updating with invalid filter', async () => { - const { sut, ctx } = setup(); - const { user } = await ctx.newUser(); - const auth = factory.auth({ user }); - - const created = await sut.create(auth, { - triggerType: PluginTriggerType.AssetCreate, - name: 'test-workflow', - description: 'Test', - enabled: true, - filters: [], - actions: [], - }); - - await expect( - sut.update(auth, created.id, { - filters: [{ pluginFilterId: factory.uuid(), filterConfig: {} }], - }), - ).rejects.toThrow(); - }); - - it('should throw error when updating with invalid action', async () => { - const { sut, ctx } = setup(); - const { user } = await ctx.newUser(); - const auth = factory.auth({ user }); - - const created = await sut.create(auth, { - triggerType: PluginTriggerType.AssetCreate, - name: 'test-workflow', - description: 'Test', - enabled: true, - filters: [], - actions: [], - }); - - await expect( - sut.update(auth, created.id, { actions: [{ pluginActionId: factory.uuid(), actionConfig: {} }] }), - ).rejects.toThrow(); - }); - - it('should update trigger type', async () => { - const { sut, ctx } = setup(); - const { user } = await ctx.newUser(); - const auth = factory.auth({ user }); - - const created = await sut.create(auth, { - triggerType: PluginTriggerType.PersonRecognized, - name: 'test-workflow', - description: 'Test', - enabled: true, - filters: [], - actions: [], - }); - - await sut.update(auth, created.id, { - triggerType: PluginTriggerType.AssetCreate, - }); - - const fetched = await sut.get(auth, created.id); - expect(fetched.triggerType).toBe(PluginTriggerType.AssetCreate); - }); - - it('should add filters', async () => { - const { sut, ctx } = setup(); - const { user } = await ctx.newUser(); - const auth = factory.auth({ user }); - - const created = await sut.create(auth, { - triggerType: PluginTriggerType.AssetCreate, - name: 'test-workflow', - description: 'Test', - enabled: true, - filters: [], - actions: [], - }); - - await sut.update(auth, created.id, { - filters: [ - { pluginFilterId: testFilterId, filterConfig: { first: true } }, - { pluginFilterId: testFilterId, filterConfig: { second: true } }, - ], - }); - - const fetched = await sut.get(auth, created.id); - expect(fetched.filters).toHaveLength(2); - expect(fetched.filters[0].filterConfig).toEqual({ first: true }); - expect(fetched.filters[1].filterConfig).toEqual({ second: true }); - }); - - it('should replace existing filters', async () => { - const { sut, ctx } = setup(); - const { user } = await ctx.newUser(); - const auth = factory.auth({ user }); - - const created = await sut.create(auth, { - triggerType: PluginTriggerType.AssetCreate, - name: 'test-workflow', - description: 'Test', - enabled: true, - filters: [{ pluginFilterId: testFilterId, filterConfig: { original: true } }], - actions: [], - }); - - await sut.update(auth, created.id, { - filters: [{ pluginFilterId: testFilterId, filterConfig: { replaced: true } }], - }); - - const fetched = await sut.get(auth, created.id); - expect(fetched.filters).toHaveLength(1); - expect(fetched.filters[0].filterConfig).toEqual({ replaced: true }); - }); - - it('should remove existing filters', async () => { - const { sut, ctx } = setup(); - const { user } = await ctx.newUser(); - const auth = factory.auth({ user }); - - const created = await sut.create(auth, { - triggerType: PluginTriggerType.AssetCreate, - name: 'test-workflow', - description: 'Test', - enabled: true, - filters: [{ pluginFilterId: testFilterId, filterConfig: { toRemove: true } }], - actions: [], - }); - - await sut.update(auth, created.id, { - filters: [], - }); - - const fetched = await sut.get(auth, created.id); - expect(fetched.filters).toHaveLength(0); - }); - }); - - describe('delete', () => { - it('should delete a workflow', async () => { - const { sut, ctx } = setup(); - const { user } = await ctx.newUser(); - const auth = factory.auth({ user }); - - const workflow = await sut.create(auth, { - triggerType: PluginTriggerType.AssetCreate, - name: 'test-workflow', - description: 'Test', - enabled: true, - filters: [], - actions: [], - }); - - await sut.delete(auth, workflow.id); - - await expect(sut.get(auth, workflow.id)).rejects.toThrow('Not found or no workflow.read access'); - }); - - it('should delete workflow with filters and actions', async () => { - const { sut, ctx } = setup(); - const { user } = await ctx.newUser(); - const auth = factory.auth({ user }); - - const workflow = await sut.create(auth, { - triggerType: PluginTriggerType.AssetCreate, - name: 'test-workflow', - description: 'Test', - enabled: true, - filters: [{ pluginFilterId: testFilterId, filterConfig: {} }], - actions: [{ pluginActionId: testActionId, actionConfig: {} }], - }); - - await sut.delete(auth, workflow.id); - - await expect(sut.get(auth, workflow.id)).rejects.toThrow('Not found or no workflow.read access'); - }); - - it('should throw error when deleting non-existent workflow', async () => { - const { sut, ctx } = setup(); - const { user } = await ctx.newUser(); - const auth = factory.auth({ user }); - - await expect(sut.delete(auth, factory.uuid())).rejects.toThrow(); - }); - - it('should throw error when user does not have access to delete workflow', async () => { - const { sut, ctx } = setup(); - const { user: user1 } = await ctx.newUser(); - const { user: user2 } = await ctx.newUser(); - const auth1 = factory.auth({ user: user1 }); - const auth2 = factory.auth({ user: user2 }); - - const workflow = await sut.create(auth1, { - triggerType: PluginTriggerType.AssetCreate, - name: 'private-workflow', - description: 'Private', - enabled: true, - filters: [], - actions: [], - }); - - await expect(sut.delete(auth2, workflow.id)).rejects.toThrow(); - }); - }); }); diff --git a/server/test/medium/specs/sync/sync-album-asset.spec.ts b/server/test/medium/specs/sync/sync-album-asset.spec.ts index 1418f35589..a3ae5fefd2 100644 --- a/server/test/medium/specs/sync/sync-album-asset.spec.ts +++ b/server/test/medium/specs/sync/sync-album-asset.spec.ts @@ -47,6 +47,7 @@ describe(SyncRequestType.AlbumAssetsV2, () => { fileCreatedAt: date, fileModifiedAt: date, localDateTime: date, + createdAt: date, deletedAt: null, duration: 600_000, livePhotoVideoId: null, @@ -73,6 +74,7 @@ describe(SyncRequestType.AlbumAssetsV2, () => { deletedAt: asset.deletedAt, fileCreatedAt: asset.fileCreatedAt, fileModifiedAt: asset.fileModifiedAt, + createdAt: asset.createdAt, isFavorite: asset.isFavorite, localDateTime: asset.localDateTime, type: asset.type, diff --git a/server/test/medium/specs/sync/sync-asset.spec.ts b/server/test/medium/specs/sync/sync-asset.spec.ts index 8b06036854..8a81b34b2a 100644 --- a/server/test/medium/specs/sync/sync-asset.spec.ts +++ b/server/test/medium/specs/sync/sync-asset.spec.ts @@ -34,6 +34,7 @@ describe(SyncEntityType.AssetV2, () => { fileCreatedAt: date, fileModifiedAt: date, localDateTime: date, + createdAt: date, deletedAt: null, duration: 600_000, libraryId: null, @@ -54,6 +55,7 @@ describe(SyncEntityType.AssetV2, () => { deletedAt: asset.deletedAt, fileCreatedAt: asset.fileCreatedAt, fileModifiedAt: asset.fileModifiedAt, + createdAt: asset.createdAt, isFavorite: asset.isFavorite, localDateTime: asset.localDateTime, type: asset.type, diff --git a/server/test/medium/specs/sync/sync-partner-asset.spec.ts b/server/test/medium/specs/sync/sync-partner-asset.spec.ts index face352970..7210e7b282 100644 --- a/server/test/medium/specs/sync/sync-partner-asset.spec.ts +++ b/server/test/medium/specs/sync/sync-partner-asset.spec.ts @@ -38,6 +38,7 @@ describe(SyncRequestType.PartnerAssetsV2, () => { fileCreatedAt: date, fileModifiedAt: date, localDateTime: date, + createdAt: date, deletedAt: null, duration: 600_000, libraryId: null, @@ -58,6 +59,7 @@ describe(SyncRequestType.PartnerAssetsV2, () => { deletedAt: null, fileCreatedAt: date, fileModifiedAt: date, + createdAt: date, isFavorite: false, localDateTime: date, type: asset.type, diff --git a/server/test/medium/specs/workflow/workflow-core-plugin.spec.ts b/server/test/medium/specs/workflow/workflow-core-plugin.spec.ts new file mode 100644 index 0000000000..2bb9de6af1 --- /dev/null +++ b/server/test/medium/specs/workflow/workflow-core-plugin.spec.ts @@ -0,0 +1,335 @@ +import { WorkflowStepConfig, WorkflowTrigger } from '@immich/plugin-sdk'; +import { Kysely } from 'kysely'; +import { readFileSync } from 'node:fs'; +import { PluginManifestDto } from 'src/dtos/plugin-manifest.dto'; +import { AssetVisibility, LogLevel } from 'src/enum'; +import { AccessRepository } from 'src/repositories/access.repository'; +import { AlbumRepository } from 'src/repositories/album.repository'; +import { AssetRepository } from 'src/repositories/asset.repository'; +import { ConfigRepository } from 'src/repositories/config.repository'; +import { CryptoRepository } from 'src/repositories/crypto.repository'; +import { DatabaseRepository } from 'src/repositories/database.repository'; +import { LoggingRepository } from 'src/repositories/logging.repository'; +import { PluginRepository } from 'src/repositories/plugin.repository'; +import { StorageRepository } from 'src/repositories/storage.repository'; +import { UserRepository } from 'src/repositories/user.repository'; +import { WorkflowRepository } from 'src/repositories/workflow.repository'; +import { DB } from 'src/schema'; +import { WorkflowExecutionService } from 'src/services/workflow-execution.service'; +import { resolveMethod } from 'src/utils/workflow'; +import { MediumTestContext } from 'test/medium.factory'; +import { mockEnvData } from 'test/repositories/config.repository.mock'; +import { getKyselyDB } from 'test/utils'; + +let initialized = false; + +class WorkflowTestContext extends MediumTestContext { + constructor(database: Kysely) { + super(WorkflowExecutionService, { + database, + real: [ + AccessRepository, + AlbumRepository, + AssetRepository, + CryptoRepository, + DatabaseRepository, + LoggingRepository, + PluginRepository, + StorageRepository, + UserRepository, + WorkflowRepository, + ], + mock: [ConfigRepository], + }); + } + + async init() { + if (initialized) { + return; + } + + const mockData = mockEnvData({}); + mockData.resourcePaths.corePlugin = '../packages/plugin-core'; + mockData.plugins.external.allow = false; + this.getMock(ConfigRepository).getEnv.mockReturnValue(mockData); + this.get(LoggingRepository).setLogLevel(LogLevel.Verbose); + + await this.sut.onPluginSync(); + await this.sut.onPluginLoad(); + + initialized = true; + } +} + +type WorkflowTemplate = { + ownerId: string; + trigger: WorkflowTrigger; + steps: WorkflowTemplateStep[]; +}; + +type WorkflowTemplateStep = { + method: string; + config?: WorkflowStepConfig; +}; + +const createWorkflow = async (template: WorkflowTemplate) => { + const workflowRepo = ctx.get(WorkflowRepository); + const pluginRepo = ctx.get(PluginRepository); + + const methods = await pluginRepo.getForValidation(); + const steps = template.steps.map((step) => { + const pluginMethod = resolveMethod(methods, step.method); + if (!pluginMethod) { + throw new Error(`Plugin method not found: ${step.method}`); + } + + return { ...step, pluginMethod }; + }); + + return workflowRepo.create( + { + enabled: true, + name: 'Test workflow', + description: 'A workflow to test the core plugin', + ownerId: template.ownerId, + trigger: template.trigger, + }, + steps.map((step) => ({ + enabled: true, + pluginMethodId: step.pluginMethod.id, + config: step.config, + })), + ); +}; + +let ctx: WorkflowTestContext; + +beforeAll(async () => { + const db = await getKyselyDB(); + ctx = new WorkflowTestContext(db); + await ctx.init(); +}, 30_000); + +describe('core plugin', () => { + describe('validation', () => { + it('should have a valid manifest.json', () => { + const buffer = readFileSync('../packages/plugin-core/manifest.json'); + const result = PluginManifestDto.schema.safeParse(JSON.parse(buffer.toString())); + if (!result.success) { + const issues = + 'error' in result + ? result.error.issues.map((issue) => ` - [${issue.path.join('.')}] ${issue.message}`).join('\n') + : ''; + const message = `Invalid packages/plugin-core/manifest.json:\n${issues}`; + expect(result.success, message).toBe(true); + } + + expect(result.success).toBe(true); + }); + }); + + describe('assetArchive', () => { + it('should archive an asset', async () => { + const { user } = await ctx.newUser(); + const { asset } = await ctx.newAsset({ ownerId: user.id }); + + const workflow = await createWorkflow({ + ownerId: user.id, + trigger: WorkflowTrigger.AssetCreate, + steps: [{ method: 'immich-plugin-core#assetArchive' }], + }); + + await expect(ctx.sut.handleAssetTrigger({ workflowId: workflow.id, assetId: asset.id })).resolves.toBeUndefined(); + + await expect(ctx.get(AssetRepository).getById(asset.id)).resolves.toMatchObject({ + visibility: AssetVisibility.Archive, + }); + }); + + it('should unarchive an asset', async () => { + const { user } = await ctx.newUser(); + const { asset } = await ctx.newAsset({ ownerId: user.id, visibility: AssetVisibility.Archive }); + + const workflow = await createWorkflow({ + ownerId: user.id, + trigger: WorkflowTrigger.AssetCreate, + steps: [{ method: 'immich-plugin-core#assetArchive', config: { inverse: true } }], + }); + + await expect(ctx.sut.handleAssetTrigger({ workflowId: workflow.id, assetId: asset.id })).resolves.toBeUndefined(); + + await expect(ctx.get(AssetRepository).getById(asset.id)).resolves.toMatchObject({ + visibility: AssetVisibility.Timeline, + }); + }); + }); + + describe('assetLock', () => { + it('should lock an asset', async () => { + const { user } = await ctx.newUser(); + const { asset } = await ctx.newAsset({ ownerId: user.id }); + + const workflow = await createWorkflow({ + ownerId: user.id, + trigger: WorkflowTrigger.AssetCreate, + steps: [{ method: 'immich-plugin-core#assetLock' }], + }); + + await expect(ctx.sut.handleAssetTrigger({ workflowId: workflow.id, assetId: asset.id })).resolves.toBeUndefined(); + + await expect(ctx.get(AssetRepository).getById(asset.id)).resolves.toMatchObject({ + visibility: AssetVisibility.Locked, + }); + }); + + it('should unlock an asset', async () => { + const { user } = await ctx.newUser(); + const { asset } = await ctx.newAsset({ ownerId: user.id, visibility: AssetVisibility.Locked }); + + const workflow = await createWorkflow({ + ownerId: user.id, + trigger: WorkflowTrigger.AssetCreate, + steps: [{ method: 'immich-plugin-core#assetLock', config: { inverse: true } }], + }); + + await expect(ctx.sut.handleAssetTrigger({ workflowId: workflow.id, assetId: asset.id })).resolves.toBeUndefined(); + + await expect(ctx.get(AssetRepository).getById(asset.id)).resolves.toMatchObject({ + visibility: AssetVisibility.Timeline, + }); + }); + }); + + describe('assetFavorite', () => { + it('should favorite an asset', async () => { + const { user } = await ctx.newUser(); + const { asset } = await ctx.newAsset({ ownerId: user.id }); + + const workflow = await createWorkflow({ + ownerId: user.id, + trigger: WorkflowTrigger.AssetCreate, + steps: [{ method: 'immich-plugin-core#assetFavorite' }], + }); + + await expect(ctx.sut.handleAssetTrigger({ workflowId: workflow.id, assetId: asset.id })).resolves.toBeUndefined(); + + await expect(ctx.get(AssetRepository).getById(asset.id)).resolves.toMatchObject({ isFavorite: true }); + }); + + it('should unfavorite an asset', async () => { + const { user } = await ctx.newUser(); + const { asset } = await ctx.newAsset({ ownerId: user.id, isFavorite: true }); + + const workflow = await createWorkflow({ + ownerId: user.id, + trigger: WorkflowTrigger.AssetCreate, + steps: [{ method: 'immich-plugin-core#assetFavorite', config: { inverse: true } }], + }); + + await expect(ctx.sut.handleAssetTrigger({ workflowId: workflow.id, assetId: asset.id })).resolves.toBeUndefined(); + + await expect(ctx.get(AssetRepository).getById(asset.id)).resolves.toMatchObject({ isFavorite: false }); + }); + }); + + describe('assetAddToAlbums', () => { + it('should create an album by name', async () => { + const { user } = await ctx.newUser(); + const { asset } = await ctx.newAsset({ ownerId: user.id, isFavorite: true }); + + const workflow = await createWorkflow({ + ownerId: user.id, + trigger: WorkflowTrigger.AssetCreate, + steps: [{ method: 'immich-plugin-core#assetAddToAlbums', config: { albumIds: [], albumName: 'Screenshots' } }], + }); + + await expect(ctx.sut.handleAssetTrigger({ workflowId: workflow.id, assetId: asset.id })).resolves.toBeUndefined(); + + const albums = await ctx.get(AlbumRepository).getAll(user.id); + expect(albums).toHaveLength(1); + + const album = albums[0]!; + expect(album.albumName).toEqual('Screenshots'); + + const updated = await ctx.get(WorkflowRepository).get(workflow.id); + expect(updated?.steps[0].config).toEqual({ albumIds: [album.id], albumName: 'Screenshots' }); + + await expect(ctx.get(AlbumRepository).getAssetIds(album.id, [asset.id])).resolves.toContain(asset.id); + }); + + it('should not use the name when there is an albumId', async () => { + const { user } = await ctx.newUser(); + const { asset } = await ctx.newAsset({ ownerId: user.id, isFavorite: true }); + const { album } = await ctx.newAlbum({ ownerId: user.id }); + + const workflow = await createWorkflow({ + ownerId: user.id, + trigger: WorkflowTrigger.AssetCreate, + steps: [ + { method: 'immich-plugin-core#assetAddToAlbums', config: { albumIds: [album.id], albumName: 'Screenshots' } }, + ], + }); + + const albums = await ctx.get(AlbumRepository).getAll(user.id); + expect(albums).toHaveLength(1); + expect(albums[0].albumName).toEqual(album.albumName); + + await expect(ctx.sut.handleAssetTrigger({ workflowId: workflow.id, assetId: asset.id })).resolves.toBeUndefined(); + + await expect(ctx.get(AlbumRepository).getAssetIds(album.id, [asset.id])).resolves.toContain(asset.id); + }); + + it('should add an asset to an album', async () => { + const { user } = await ctx.newUser(); + const { asset } = await ctx.newAsset({ ownerId: user.id, isFavorite: true }); + const { album } = await ctx.newAlbum({ ownerId: user.id }); + + const workflow = await createWorkflow({ + ownerId: user.id, + trigger: WorkflowTrigger.AssetCreate, + steps: [{ method: 'immich-plugin-core#assetAddToAlbums', config: { albumIds: [album.id] } }], + }); + + await expect(ctx.sut.handleAssetTrigger({ workflowId: workflow.id, assetId: asset.id })).resolves.toBeUndefined(); + + await expect(ctx.get(AlbumRepository).getAssetIds(album.id, [asset.id])).resolves.toContain(asset.id); + }); + + it('should add an asset to multiple albums', async () => { + const { user } = await ctx.newUser(); + const [{ asset }, { album: album1 }, { album: album2 }] = await Promise.all([ + ctx.newAsset({ ownerId: user.id, isFavorite: true }), + ctx.newAlbum({ ownerId: user.id }), + ctx.newAlbum({ ownerId: user.id }), + ]); + + const workflow = await createWorkflow({ + ownerId: user.id, + trigger: WorkflowTrigger.AssetCreate, + steps: [{ method: 'immich-plugin-core#assetAddToAlbums', config: { albumIds: [album1.id, album2.id] } }], + }); + + await expect(ctx.sut.handleAssetTrigger({ workflowId: workflow.id, assetId: asset.id })).resolves.toBeUndefined(); + + await expect(ctx.get(AlbumRepository).getAssetIds(album1.id, [asset.id])).resolves.toContain(asset.id); + await expect(ctx.get(AlbumRepository).getAssetIds(album2.id, [asset.id])).resolves.toContain(asset.id); + }); + + it('should require album access', async () => { + const { user: user1 } = await ctx.newUser(); + const { user: user2 } = await ctx.newUser(); + const { asset } = await ctx.newAsset({ ownerId: user1.id, isFavorite: true }); + const { album } = await ctx.newAlbum({ ownerId: user2.id }); + + const workflow = await createWorkflow({ + ownerId: user1.id, + trigger: WorkflowTrigger.AssetCreate, + steps: [{ method: 'immich-plugin-core#assetAddToAlbums', config: { albumIds: [album.id] } }], + }); + + await expect(ctx.sut.handleAssetTrigger({ workflowId: workflow.id, assetId: asset.id })).resolves.toBeTruthy(); + + await expect(ctx.get(AlbumRepository).getAssetIds(album.id, [asset.id])).resolves.not.toContain(asset.id); + }); + }); +}); diff --git a/server/test/repositories/asset.repository.mock.ts b/server/test/repositories/asset.repository.mock.ts index e3a1dbdf05..0540128908 100644 --- a/server/test/repositories/asset.repository.mock.ts +++ b/server/test/repositories/asset.repository.mock.ts @@ -31,6 +31,7 @@ export const newAssetRepositoryMock = (): Mocked['readJsonFile'], + readdirWithTypes: vitest.fn(), createFile: vitest.fn(), createWriteStream: vitest.fn(), createOrOverwriteFile: vitest.fn(), @@ -74,5 +75,6 @@ export const newStorageRepositoryMock = (): Mocked ({ close: vitest.fn(), on: vitest.fn() })), }; }; diff --git a/server/test/utils.ts b/server/test/utils.ts index 791a457783..e707971aad 100644 --- a/server/test/utils.ts +++ b/server/test/utils.ts @@ -181,7 +181,11 @@ export const automock = ( const mocks: Mock[] = []; const instance = new Dependency(...args); - for (const property of Object.getOwnPropertyNames(Dependency.prototype)) { + const propertyNames = new Set([ + ...Object.getOwnPropertyNames(Dependency.prototype), + ...Object.getOwnPropertyNames(instance), + ]); + for (const property of propertyNames) { if (property === 'constructor') { continue; } @@ -326,7 +330,7 @@ export const getMocks = () => { oauth: automock(OAuthRepository, { args: [loggerMock] }), partner: automock(PartnerRepository, { strict: false }), person: automock(PersonRepository, { strict: false }), - plugin: automock(PluginRepository, { strict: true }), + plugin: automock(PluginRepository, { strict: true, args: [databaseMock, loggerMock] }), process: automock(ProcessRepository), search: automock(SearchRepository, { strict: false }), // eslint-disable-next-line no-sparse-arrays @@ -346,7 +350,7 @@ export const getMocks = () => { trash: automock(TrashRepository), user: automock(UserRepository, { strict: false }), versionHistory: automock(VersionHistoryRepository), - videoStream: automock(VideoStreamRepository), + videoStream: automock(VideoStreamRepository, { strict: false }), view: automock(ViewRepository), // eslint-disable-next-line no-sparse-arrays websocket: automock(WebsocketRepository, { args: [, loggerMock], strict: false }), @@ -500,6 +504,7 @@ export const mockSpawn = vitest.fn((exitCode: number, stdout: string, stderr: st callback(exitCode); } }), + kill: vitest.fn(), } as unknown as ChildProcessWithoutNullStreams; }); diff --git a/server/tsconfig.json b/server/tsconfig.json index e087544f6b..c3aede3e5b 100644 --- a/server/tsconfig.json +++ b/server/tsconfig.json @@ -8,9 +8,9 @@ "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "resolveJsonModule": true, - "target": "es2022", + "target": "es2024", "moduleResolution": "node16", - "lib": ["dom", "es2023"], + "lib": ["dom", "es2024"], "sourceMap": true, "outDir": "./dist", "incremental": true, diff --git a/web/.nvmrc b/web/.nvmrc deleted file mode 100644 index 5bf4400f22..0000000000 --- a/web/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -24.15.0 diff --git a/web/mise.toml b/web/mise.toml index 00b2b30c6b..b0d41317cb 100644 --- a/web/mise.toml +++ b/web/mise.toml @@ -42,11 +42,17 @@ run = "pnpm run check:svelte" [tasks.check] run = { tasks = [":check-typescript", ":check-svelte"] } -[tasks.checklist] +[tasks.ci-unit] +depends = ["//:sdk:install", "//:sdk:build"] run = [ { task = ":install" }, { task = ":format" }, { task = ":check" }, { task = ":test --run" }, +] + +[tasks.checklist] +run = [ + { task = ":ci-unit" }, { task = ":lint" }, ] diff --git a/web/package.json b/web/package.json index daaa74d9e9..62c5a43bd0 100644 --- a/web/package.json +++ b/web/package.json @@ -14,7 +14,7 @@ "check:watch": "pnpm run check:svelte --watch", "check:code": "pnpm run format && pnpm run lint && pnpm run check:svelte && pnpm run check:typescript", "check:all": "pnpm run check:code && pnpm run test:cov", - "lint": "eslint . --max-warnings 0 --concurrency 4", + "lint": "eslint . --max-warnings 0 --concurrency 6", "lint:fix": "pnpm run lint --fix", "format": "prettier --cache --check .", "format:fix": "prettier --cache --write --list-different .", @@ -27,7 +27,7 @@ "@formatjs/icu-messageformat-parser": "^3.0.0", "@immich/justified-layout-wasm": "^0.4.3", "@immich/sdk": "workspace:*", - "@immich/ui": "^0.76.0", + "@immich/ui": "^0.79.2", "@mapbox/mapbox-gl-rtl-text": "0.4.0", "@mdi/js": "^7.4.47", "@noble/hashes": "^2.2.0", @@ -46,6 +46,8 @@ "geojson": "^0.5.0", "handlebars": "^4.7.8", "happy-dom": "^20.0.0", + "hls-video-element": "^1.5.11", + "hls.js": "^1.6.16", "intl-messageformat": "^11.0.0", "justified-layout": "^4.1.0", "lodash-es": "^4.17.21", @@ -76,7 +78,7 @@ "@sveltejs/adapter-static": "^3.0.8", "@sveltejs/enhanced-img": "^0.10.4", "@sveltejs/kit": "^2.56.1", - "@sveltejs/vite-plugin-svelte": "7.0.0", + "@sveltejs/vite-plugin-svelte": "7.1.2", "@tailwindcss/vite": "^4.2.4", "@testing-library/jest-dom": "^6.4.2", "@testing-library/svelte": "^5.2.8", @@ -101,9 +103,9 @@ "happy-dom": "^20.0.0", "prettier": "^3.7.4", "prettier-plugin-sort-json": "^4.1.1", - "prettier-plugin-svelte": "^3.3.3", + "prettier-plugin-svelte": "^4.0.0", "rollup-plugin-visualizer": "^7.0.0", - "svelte": "5.55.2", + "svelte": "5.55.8", "svelte-check": "^4.4.6", "svelte-eslint-parser": "^1.3.3", "tailwindcss": "^4.2.4", @@ -111,8 +113,5 @@ "typescript-eslint": "^8.45.0", "vite": "^8.0.0", "vitest": "^4.0.0" - }, - "volta": { - "node": "24.15.0" } } diff --git a/web/src/app.css b/web/src/app.css index 07226be41f..4435b255d2 100644 --- a/web/src/app.css +++ b/web/src/app.css @@ -34,7 +34,7 @@ } @utility immich-form-input { - @apply bg-gray-100 ring-1 ring-gray-200 transition outline-none focus-within:ring-1 disabled:cursor-not-allowed dark:bg-gray-800 dark:ring-neutral-900 flex w-full items-center rounded-lg disabled:bg-gray-300 disabled:text-dark dark:disabled:bg-gray-900 dark:disabled:text-gray-200 flex-1 py-2.5 text-base pl-4 pr-4; + @apply bg-gray-100 ring-1 ring-gray-200 transition outline-none focus-within:ring-primary focus-within:ring-1 disabled:cursor-not-allowed dark:bg-gray-800 dark:ring-neutral-900 dark:focus-within:ring-primary flex w-full items-center rounded-lg disabled:bg-gray-300 disabled:text-dark dark:disabled:bg-gray-900 dark:disabled:text-gray-200 flex-1 py-2.5 text-base pl-4 pr-4; } @utility immich-form-label { diff --git a/web/src/lib/commands.ts b/web/src/lib/commands.ts index 59c25179f2..7498756ac0 100644 --- a/web/src/lib/commands.ts +++ b/web/src/lib/commands.ts @@ -1,17 +1,36 @@ import { defaultProvider, screencastManager, themeManager, ThemePreference, type ActionItem } from '@immich/ui'; import { mdiAccountMultipleOutline, + mdiAccountOutline, + mdiArchiveArrowDownOutline, mdiBookshelf, mdiCog, + mdiContentDuplicate, + mdiCrosshairsGps, + mdiFolderOutline, + mdiHeartOutline, + mdiImageAlbum, + mdiImageMultipleOutline, + mdiImageSizeSelectLarge, mdiKeyboard, + mdiLink, + mdiLockOutline, + mdiMagnify, + mdiMapMarkerOutline, + mdiMapOutline, mdiServer, + mdiStateMachine, mdiSync, + mdiTagMultipleOutline, mdiThemeLightDark, + mdiToolboxOutline, + mdiTrashCanOutline, } from '@mdi/js'; import type { MessageFormatter } from 'svelte-i18n'; import { goto } from '$app/navigation'; import { page } from '$app/state'; import { authManager } from '$lib/managers/auth-manager.svelte'; +import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte'; import { Route } from '$lib/route'; import { copyToClipboard } from '$lib/utils'; @@ -49,7 +68,138 @@ export const getPagesProvider = ($t: MessageFormatter) => { }, ].map((route) => ({ ...route, $if: () => authManager.authenticated && authManager.user.isAdmin })); - return defaultProvider({ name: $t('page'), actions: adminPages }); + const userPages: ActionItem[] = [ + { + title: $t('photos'), + icon: mdiImageMultipleOutline, + onAction: () => goto(Route.photos()), + }, + { + title: $t('explore'), + icon: mdiMagnify, + onAction: () => goto(Route.explore()), + $if: () => authManager.authenticated && featureFlagsManager.value.search, + }, + + { + title: $t('map'), + icon: mdiMapOutline, + onAction: () => goto(Route.map()), + $if: () => authManager.authenticated && featureFlagsManager.value.map, + }, + { + title: $t('people'), + description: $t('people_feature_description'), + icon: mdiAccountOutline, + onAction: () => goto(Route.people()), + $if: () => authManager.authenticated && authManager.preferences.people.enabled, + }, + { + title: $t('places'), + icon: mdiMapMarkerOutline, + onAction: () => goto(Route.places()), + }, + { + title: $t('shared_links'), + icon: mdiLink, + onAction: () => goto(Route.sharedLinks()), + $if: () => authManager.authenticated && authManager.preferences.sharedLinks.enabled, + }, + { + title: $t('recently_added'), + icon: mdiMagnify, + onAction: () => goto(Route.recentlyAdded()), + $if: () => authManager.authenticated, + }, + { + title: $t('sharing'), + icon: mdiAccountMultipleOutline, + onAction: () => goto(Route.sharing()), + $if: () => authManager.authenticated, + }, + { + title: $t('favorites'), + icon: mdiHeartOutline, + onAction: () => goto(Route.favorites()), + $if: () => authManager.authenticated, + }, + { + title: $t('albums'), + description: $t('albums_feature_description'), + icon: mdiImageAlbum, + onAction: () => goto(Route.albums()), + $if: () => authManager.authenticated, + }, + { + title: $t('tags'), + description: $t('tag_feature_description'), + icon: mdiTagMultipleOutline, + onAction: () => goto(Route.tags()), + $if: () => authManager.authenticated && authManager.preferences.tags.enabled, + }, + { + title: $t('folders'), + description: $t('folders_feature_description'), + icon: mdiFolderOutline, + onAction: () => goto(Route.folders()), + $if: () => authManager.authenticated && authManager.preferences.folders.enabled, + }, + { + title: $t('utilities'), + icon: mdiToolboxOutline, + onAction: () => goto(Route.utilities()), + $if: () => authManager.authenticated, + }, + { + title: $t('archive'), + icon: mdiArchiveArrowDownOutline, + onAction: () => goto(Route.archive()), + $if: () => authManager.authenticated, + }, + { + title: $t('locked_folder'), + icon: mdiLockOutline, + onAction: () => goto(Route.locked()), + $if: () => authManager.authenticated, + }, + { + title: $t('trash'), + icon: mdiTrashCanOutline, + onAction: () => goto(Route.trash()), + $if: () => authManager.authenticated && featureFlagsManager.value.trash, + }, + { + title: $t('admin.user_settings'), + icon: mdiCog, + onAction: () => goto(Route.userSettings()), + $if: () => authManager.authenticated, + }, + ].map((route) => ({ $if: () => authManager.authenticated, ...route })); + + const utilityPages: ActionItem[] = [ + { + title: $t('review_duplicates'), + icon: mdiContentDuplicate, + onAction: () => goto(Route.duplicatesUtility()), + }, + { + title: $t('review_large_files'), + icon: mdiImageSizeSelectLarge, + onAction: () => goto(Route.largeFileUtility()), + }, + { + title: $t('manage_geolocation'), + icon: mdiCrosshairsGps, + onAction: () => goto(Route.geolocationUtility()), + }, + { + title: $t('workflows'), + icon: mdiStateMachine, + onAction: () => goto(Route.workflows()), + }, + ].map((route) => ({ ...route, $if: () => authManager.authenticated })); + + return defaultProvider({ name: $t('page'), actions: [...userPages, ...utilityPages, ...adminPages] }); }; const getMyImmichLink = () => { diff --git a/web/src/lib/components/AdaptiveImage.svelte b/web/src/lib/components/AdaptiveImage.svelte index 39bc4516b7..04136c721c 100644 --- a/web/src/lib/components/AdaptiveImage.svelte +++ b/web/src/lib/components/AdaptiveImage.svelte @@ -1,3 +1,54 @@ + + {#if action.$if?.() ?? true} +{/snippet} + +
+
+
+ + {#if array} +
+ {#each albumIds as albumId, i (i)} + { + albumIds.splice(i, 1); + albumIds = [...albumIds]; + }} + /> + {/each} + {@render button()} +
+ {:else} + {@const albumId = albumIds[0]} + {#if albumId} + (albumIds = [])} /> + {:else} + {@render button()} + {/if} + {/if} +
diff --git a/web/src/lib/components/SchemaConfiguration.svelte b/web/src/lib/components/SchemaConfiguration.svelte new file mode 100644 index 0000000000..2cc0b68089 --- /dev/null +++ b/web/src/lib/components/SchemaConfiguration.svelte @@ -0,0 +1,99 @@ + + + +{#if Object.keys(schema).length === 0} + + +{:else if schema.type === 'object'} + {#if !root} +
+ + {#if description} + {description} + {/if} +
+ {/if} +
+ {#each Object.entries(schema.properties ?? {}) as [childKey, childSchema] (childKey)} + + {/each} +
+{:else if schema.uiHint === 'AlbumId'} + +{:else if schema.enum && schema.array} + + + +{:else if schema.enum} + + getValue(), setValue} /> + +{:else} + Unknown schema + +{/if} diff --git a/web/src/lib/components/album-page/AlbumCard.svelte b/web/src/lib/components/album-page/AlbumCard.svelte index cdb7e9e27c..e0151deda6 100644 --- a/web/src/lib/components/album-page/AlbumCard.svelte +++ b/web/src/lib/components/album-page/AlbumCard.svelte @@ -23,7 +23,7 @@ showDateRange = false, showItemCount = false, preload = false, - onShowContextMenu = undefined, + onShowContextMenu, }: Props = $props(); const showAlbumContextMenu = (e: MouseEvent) => { diff --git a/web/src/lib/components/album-page/AlbumThumbnail.svelte b/web/src/lib/components/album-page/AlbumThumbnail.svelte new file mode 100644 index 0000000000..e585809dec --- /dev/null +++ b/web/src/lib/components/album-page/AlbumThumbnail.svelte @@ -0,0 +1,67 @@ + + +
+ {#await getAlbumInfo({ ...authManager.params, id: albumId })} + + {:then album} +
+ +

+ {album.albumName} +

+ {#if album.description} +

+ {album.description} +

+ {/if} +
+ +
+
+ {:catch} +
+
+ {$t('unknown')} + {albumId} +
+
+ +
+
+ {/await} +
diff --git a/web/src/lib/components/album-page/AlbumViewer.svelte b/web/src/lib/components/album-page/AlbumViewer.svelte index 85a032067f..4e7d6bce1a 100644 --- a/web/src/lib/components/album-page/AlbumViewer.svelte +++ b/web/src/lib/components/album-page/AlbumViewer.svelte @@ -103,7 +103,7 @@ {/if} {:else} - + {#snippet leading()} diff --git a/web/src/lib/components/asset-viewer/ActivityViewer.svelte b/web/src/lib/components/asset-viewer/ActivityViewer.svelte index cd952fe08b..803495c8bb 100644 --- a/web/src/lib/components/asset-viewer/ActivityViewer.svelte +++ b/web/src/lib/components/asset-viewer/ActivityViewer.svelte @@ -128,7 +128,7 @@ {#if innerHeight}
{#each activityManager.activities as reaction, index (reaction.id)} diff --git a/web/src/lib/components/asset-viewer/AssetViewer.spec.ts b/web/src/lib/components/asset-viewer/AssetViewer.spec.ts index 8f7c448047..a1498ed2ff 100644 --- a/web/src/lib/components/asset-viewer/AssetViewer.spec.ts +++ b/web/src/lib/components/asset-viewer/AssetViewer.spec.ts @@ -51,7 +51,7 @@ describe('AssetViewer', () => { vi.restoreAllMocks(); }); - it('updates the top bar favorite action after pressing favorite', async () => { + it.skip('updates the top bar favorite action after pressing favorite', async () => { const ownerId = 'owner-id'; const user = userAdminFactory.build({ id: ownerId }); const asset = assetFactory.build({ ownerId, isFavorite: false, isTrashed: false }); diff --git a/web/src/lib/components/asset-viewer/AssetViewer.svelte b/web/src/lib/components/asset-viewer/AssetViewer.svelte index cb49828f0d..16f7c6cfb0 100644 --- a/web/src/lib/components/asset-viewer/AssetViewer.svelte +++ b/web/src/lib/components/asset-viewer/AssetViewer.svelte @@ -1,6 +1,7 @@ + +{#if shouldShow} +
+
+
+ {#if description} + {description} + {/if} + {#if $slideshowMetadataOverlayMode !== SlideshowMetadataOverlayMode.DescriptionOnly} +
+ {#if dateString} + {dateString} + {/if} + {#if locationString} + {locationString} + {/if} +
+ {/if} +
+
+
+{/if} diff --git a/web/src/lib/components/asset-viewer/VideoNativeViewer.svelte b/web/src/lib/components/asset-viewer/VideoNativeViewer.svelte index 58e6fdd1e1..1462fd4a92 100644 --- a/web/src/lib/components/asset-viewer/VideoNativeViewer.svelte +++ b/web/src/lib/components/asset-viewer/VideoNativeViewer.svelte @@ -4,8 +4,8 @@ import { assetViewerFadeDuration } from '$lib/constants'; import { assetViewerManager } from '$lib/managers/asset-viewer-manager.svelte'; import { castManager } from '$lib/managers/cast-manager.svelte'; - import { autoPlayVideo, loopVideo as loopVideoPreference } from '$lib/stores/preferences.store'; - import { getAssetMediaUrl, getAssetPlaybackUrl } from '$lib/utils'; + import { autoPlayVideo, lang, loopVideo as loopVideoPreference } from '$lib/stores/preferences.store'; + import { getAssetHlsSessionUrl, getAssetHlsUrl, getAssetMediaUrl, getAssetPlaybackUrl } from '$lib/utils'; import { AssetMediaSize, type AssetResponseDto } from '@immich/sdk'; import { Icon, LoadingSpinner } from '@immich/ui'; import { @@ -21,6 +21,9 @@ mdiVolumeMedium, mdiVolumeMute, } from '@mdi/js'; + import Hls, { AbrController, Events, type FragLoadedData, type FragLoadingData, type HlsConfig } from 'hls.js'; + import 'hls-video-element'; + import type HlsVideoElement from 'hls-video-element'; import 'media-chrome/media-control-bar'; import 'media-chrome/media-controller'; import 'media-chrome/media-fullscreen-button'; @@ -28,9 +31,10 @@ import 'media-chrome/media-play-button'; import 'media-chrome/media-playback-rate-button'; import 'media-chrome/media-time-display'; - import 'media-chrome/media-time-range'; + import './immich-time-range'; import 'media-chrome/media-volume-range'; import 'media-chrome/menu/media-playback-rate-menu'; + import 'media-chrome/menu/media-rendition-menu'; import 'media-chrome/menu/media-settings-menu'; import 'media-chrome/menu/media-settings-menu-button'; import 'media-chrome/menu/media-settings-menu-item'; @@ -38,6 +42,8 @@ import { useSwipe, type SwipeCustomEvent } from 'svelte-gestures'; import { t } from 'svelte-i18n'; import { fade } from 'svelte/transition'; + import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte'; + import { mediaCapabilitiesManager } from '$lib/managers/media-capabilities-manager.svelte'; interface Props { asset: AssetResponseDto; @@ -69,14 +75,155 @@ let videoPlayer: HTMLVideoElement | undefined = $state(); let isLoading = $state(true); - let assetFileUrl = $derived( - playOriginalVideo - ? getAssetMediaUrl({ id: assetId, size: AssetMediaSize.Original, cacheKey }) - : getAssetPlaybackUrl({ id: assetId, cacheKey }), - ); + let assetFileUrl = $derived.by(() => { + if (featureFlagsManager.value.realtimeTranscoding) { + return getAssetHlsUrl(assetId); + } + + if (playOriginalVideo) { + return getAssetMediaUrl({ id: assetId, size: AssetMediaSize.Original, cacheKey }); + } + + return getAssetPlaybackUrl({ id: assetId, cacheKey }); + }); const aspectRatio = $derived(asset.width && asset.height ? `${asset.width} / ${asset.height}` : undefined); let showVideo = $state(false); let hasFocused = $state(false); + let activeSession: { assetId: string; id: string } | undefined; + let rebuildCount = 0; + + const MAX_REBUILDS = 1; + const SESSION_ID_REGEX = /\/video\/stream\/([0-9a-f-]{36})\//; + + // hls.js can abandon fetching an in-flight fragment if it thinks it'll take too long, in which case + // it emergency switches to a different variant. This extends the delay even further due to + // cold starting another transcode, so let the fragment finish and have steady ABR decide the next level. + // + // It can also emergency switch between fragments: while a switch's first segment is still loading, + // it can run out of buffer and drop to a lower level for just one segment before continuing at the switched quality. + // This can cause multiple redundant transcoding restarts when it occurs. + // Hold the committed level until its first fragment lands, then resume normal ABR. + class NoAbandonAbrController extends AbrController { + private switchTarget = -1; + + protected override onFragLoading(_event: Events.FRAG_LOADING, data: FragLoadingData) { + if (data.frag.sn === 'initSegment') { + this.switchTarget = data.frag.level; + } + } + + protected override onFragLoaded(event: Events.FRAG_LOADED, data: FragLoadedData) { + if (data.frag.sn !== 'initSegment') { + this.switchTarget = -1; + } + super.onFragLoaded(event, data); + } + + override get nextAutoLevel(): number { + const level = super.nextAutoLevel; + const target = this.hls.levels[this.switchTarget]; + // Hold the committed level, but only while hls.js still considers it healthy. + if (target && level < this.switchTarget && target.loadError === 0 && target.fragmentError === 0) { + return this.switchTarget; + } + return level; + } + + override set nextAutoLevel(level: number) { + super.nextAutoLevel = level; + } + } + + const hlsConfig: Partial = { + abrController: NoAbandonAbrController, + highBufferWatchdogPeriod: 10, + detectStallWithCurrentTimeMs: 10_000, + maxBufferHole: 0.5, + maxBufferLength: 30, + maxMaxBufferLength: 60, + fragLoadPolicy: { + default: { + maxTimeToFirstByteMs: 30_000, + maxLoadTimeMs: 60_000, + timeoutRetry: { maxNumRetry: 5, retryDelayMs: 100, maxRetryDelayMs: 0 }, + errorRetry: { maxNumRetry: 3, retryDelayMs: 1000, maxRetryDelayMs: 8000 }, + }, + }, + useMediaCapabilities: false, + }; + + const releaseSession = () => { + const session = activeSession; + if (!session) { + return; + } + activeSession = undefined; + const url = getAssetHlsSessionUrl(session.assetId, session.id); + void fetch(url, { method: 'DELETE' }).catch(() => console.warn('Failed to release HLS session', session)); + }; + + const isHlsElement = (el: HTMLVideoElement | undefined): el is HlsVideoElement => { + return el?.tagName === 'HLS-VIDEO'; + }; + + const wireHlsListeners = (el: HlsVideoElement, assetId: string, resumeTime?: number) => { + const api = el.api; + if (!api) { + return; + } + + // This is a hack to make the rendition menu use `api.currentLevel` instead of `api.nextLevel`. + // `api.nextLevel` makes the player request the next segment followed by the current segment. + // That backward request causes the server to restart transcoding for no reason. + Object.defineProperty(api, 'nextLevel', { + configurable: true, + get: () => api.currentLevel, + set: (level: number) => { + api.currentLevel = level; + }, + }); + + // eslint-disable-next-line @typescript-eslint/no-misused-promises + api.on(Hls.Events.MANIFEST_PARSED, async () => { + // Defer hls.js's first fragment load until we filter out suboptimal variants + api.stopLoad(); + const id = api.levels[0]?.url[0]?.match(SESSION_ID_REGEX)?.[1]; + if (id) { + activeSession = { assetId, id }; + } + + const keep = await mediaCapabilitiesManager.efficientLevels(api.levels); + for (let i = api.levels.length - 1; i >= 0; i--) { + if (!keep.has(i)) { + api.removeLevel(i); + } + } + + api.startLoad(resumeTime); + }); + + api.on(Hls.Events.FRAG_LOADED, () => (rebuildCount = 0)); + + api.on(Hls.Events.ERROR, (_, data) => { + // 404 on a fragment can mean the server-side session has expired. Refetch + // master for a new session, but give up if it still 404s. + if ( + !data.fatal || + data.details !== Hls.ErrorDetails.FRAG_LOAD_ERROR || + data.response?.code !== 404 || + rebuildCount++ >= MAX_REBUILDS + ) { + console.error('HLS error', JSON.stringify(data)); + return; + } + console.warn('Error loading segment, starting new session'); + activeSession = undefined; + resumeTime = el.currentTime; + el.load(); + // wireHlsListeners must run after el.api is repopulated. + queueMicrotask(() => wireHlsListeners(el, assetId, resumeTime)); + }); + }; onMount(() => { showVideo = true; @@ -84,10 +231,31 @@ $effect(() => { // reactive on `assetFileUrl` changes - if (assetFileUrl) { + if (videoPlayer && assetFileUrl) { hasFocused = false; - videoPlayer?.load(); + rebuildCount = 0; + releaseSession(); + if (isHlsElement(videoPlayer)) { + videoPlayer.config = hlsConfig; + videoPlayer.src = assetFileUrl; + const el = videoPlayer; + queueMicrotask(() => wireHlsListeners(el, assetId)); + } else { + videoPlayer.load(); + } } + return releaseSession; + }); + + const onPagehide = (event: PageTransitionEvent) => { + if (!event.persisted) { + releaseSession(); + } + }; + + $effect(() => { + window.addEventListener('pagehide', onPagehide); + return () => window.removeEventListener('pagehide', onPagehide); }); onDestroy(() => { @@ -144,6 +312,10 @@ videoPlayer?.pause(); } }); + + // The time is only refreshed on HLS fragment decode by default, + // so manually emit events on seek to update it immediately. + const onSeeking = (event: Event) => event.currentTarget?.dispatchEvent(new Event('timeupdate')); {#if showVideo} @@ -166,44 +338,80 @@ - + {#if featureFlagsManager.value.realtimeTranscoding} + handleCanPlay(e.currentTarget as HTMLVideoElement)} + onended={onVideoEnded} + onseeking={onSeeking} + onplaying={(e: Event) => { + if (!hasFocused) { + (e.currentTarget as HTMLElement).focus(); + hasFocused = true; + } + }} + onclose={onClose} + poster={getAssetMediaUrl({ id: asset.id, size: AssetMediaSize.Preview, cacheKey })} + > + {:else} + + {/if} {#if extendedControls} -
@@ -247,7 +455,7 @@ {/if} - {#if assetViewerManager.isFaceEditMode} + {#if assetViewerManager.isFaceEditMode && videoPlayer} {/if} {/if} @@ -290,12 +498,12 @@ font-variant-numeric: tabular-nums; } - media-time-range, + immich-time-range, media-volume-range { --media-control-hover-background: none; } - media-time-range:hover, + immich-time-range:hover, media-volume-range:hover { --media-range-thumb-opacity: 1; } diff --git a/web/src/lib/components/asset-viewer/actions/SetPersonFeaturedAction.svelte b/web/src/lib/components/asset-viewer/actions/SetPersonFeaturedAction.svelte index 0795662fd5..86e201c613 100644 --- a/web/src/lib/components/asset-viewer/actions/SetPersonFeaturedAction.svelte +++ b/web/src/lib/components/asset-viewer/actions/SetPersonFeaturedAction.svelte @@ -1,4 +1,5 @@
((loaded = true), (thumbError = errored))} + onComplete={(errored) => { + const rect = element?.getBoundingClientRect(); + skipFade = !rect || rect.bottom < 0 || rect.top > window.innerHeight; + loaded = true; + thumbError = errored; + }} /> {#if asset.isVideo}
@@ -309,7 +303,10 @@
{:else} -
+
{#each showPeople as person (person.id)} {#if !editedFace.person || person.id !== editedFace.person.id}
diff --git a/web/src/lib/components/pages/SharedLinkPage.svelte b/web/src/lib/components/pages/SharedLinkPage.svelte index 40cf0a193f..8cb4aadd7b 100644 --- a/web/src/lib/components/pages/SharedLinkPage.svelte +++ b/web/src/lib/components/pages/SharedLinkPage.svelte @@ -91,7 +91,7 @@
- + {#snippet leading()} diff --git a/web/src/lib/components/share-page/IndividualSharedViewer.svelte b/web/src/lib/components/share-page/IndividualSharedViewer.svelte index 86b716da40..936ccb0f41 100644 --- a/web/src/lib/components/share-page/IndividualSharedViewer.svelte +++ b/web/src/lib/components/share-page/IndividualSharedViewer.svelte @@ -18,7 +18,7 @@ import { toTimelineAsset } from '$lib/utils/timeline-util'; import { getAssetInfo, type SharedLinkResponseDto } from '@immich/sdk'; import { IconButton, Logo, toastManager } from '@immich/ui'; - import { mdiArrowLeft, mdiDownload, mdiFileImagePlusOutline, mdiSelectAll } from '@mdi/js'; + import { mdiDownload, mdiFileImagePlusOutline, mdiSelectAll } from '@mdi/js'; import { t } from 'svelte-i18n'; import ControlAppBar from '../shared-components/ControlAppBar.svelte'; import GalleryViewer from '../shared-components/gallery-viewer/GalleryViewer.svelte'; @@ -97,7 +97,7 @@ {/if} {:else} - goto(Route.photos())} backIcon={mdiArrowLeft} showBackButton={false}> + {#snippet leading()} diff --git a/web/src/lib/components/shared-components/ControlAppBar.svelte b/web/src/lib/components/shared-components/ControlAppBar.svelte index b515e9a165..eb021be775 100644 --- a/web/src/lib/components/shared-components/ControlAppBar.svelte +++ b/web/src/lib/components/shared-components/ControlAppBar.svelte @@ -1,97 +1,49 @@ -
- + {#if trailing} + + {@render trailing()} + + {/if} +
diff --git a/web/src/lib/components/shared-components/context-menu/ContextMenu.svelte b/web/src/lib/components/shared-components/context-menu/ContextMenu.svelte index 9d849cfbfc..729a4a4ea9 100644 --- a/web/src/lib/components/shared-components/context-menu/ContextMenu.svelte +++ b/web/src/lib/components/shared-components/context-menu/ContextMenu.svelte @@ -64,7 +64,7 @@
void) | undefined; + onEndReached?: (() => void) | undefined; showAssetName?: boolean; onReload?: (() => void) | undefined; pageHeaderOffset?: number; @@ -50,7 +55,7 @@ disableAssetSelect = false, showArchiveIcon = false, viewport, - onIntersected = undefined, + onEndReached = undefined, showAssetName = false, onReload = undefined, slidingWindowOffset = 0, @@ -70,24 +75,23 @@ }), ); - const getStyle = (i: number) => { - const geo = geometry; - return `top: ${geo.getTop(i)}px; left: ${geo.getLeft(i)}px; width: ${geo.getWidth(i)}px; height: ${geo.getHeight(i)}px;`; + const getStyle = (index: number) => { + return `top: ${geometry.getTop(index)}px; left: ${geometry.getLeft(index)}px; width: ${geometry.getWidth(index)}px; height: ${geometry.getHeight(index)}px;`; }; - const isIntersecting = (i: number) => { - const geo = geometry; + const isInOrNearViewport = (index: number) => { const window = slidingWindow; - const top = geo.getTop(i); - return top + pageHeaderOffset < window.bottom && top + geo.getHeight(i) > window.top; + const top = geometry.getTop(index); + return top + pageHeaderOffset < window.bottom && top + geometry.getHeight(index) > window.top; }; let shiftKeyIsDown = $state(false); let lastAssetMouseEvent: TimelineAsset | null = $state(null); let scrollTop = $state(0); + let slidingWindow = $derived.by(() => { - const top = (scrollTop || 0) - slidingWindowOffset; - const bottom = top + viewport.height + slidingWindowOffset; + const top = (scrollTop || 0) - slidingWindowOffset - INTERSECTION_EXPAND_TOP; + const bottom = top + viewport.height + slidingWindowOffset + INTERSECTION_EXPAND_BOTTOM; return { top, bottom, @@ -101,17 +105,15 @@ const updateSlidingWindow = () => (scrollTop = document.scrollingElement?.scrollTop ?? 0); - const debouncedOnIntersected = debounce(() => onIntersected?.(), 750, { maxWait: 100, leading: true }); + const debouncedOnEndReached = debounce(() => onEndReached?.(), 750, { maxWait: 100, leading: true }); - let lastIntersectedHeight = 0; + let lastEndReachedHeight = 0; $effect(() => { - // Intersect if there's only one viewport worth of assets left to scroll. if (geometry.containerHeight - slidingWindow.bottom <= viewport.height) { - // Notify we got to (near) the end of scroll. - const intersectedHeight = geometry.containerHeight; - if (lastIntersectedHeight !== intersectedHeight) { - debouncedOnIntersected(); - lastIntersectedHeight = intersectedHeight; + const contentHeight = geometry.containerHeight; + if (lastEndReachedHeight !== contentHeight) { + debouncedOnEndReached(); + lastEndReachedHeight = contentHeight; } } }); @@ -362,10 +364,10 @@ style:height={geometry.containerHeight + 'px'} style:width={geometry.containerWidth + 'px'} > - {#each assets as asset, i (asset.id + '-' + i)} - {#if isIntersecting(i)} + {#each assets as asset, index (asset.id + '-' + index)} + {#if isInOrNearViewport(index)} {@const currentAsset = toTimelineAsset(asset)} -
+
{ @@ -382,8 +384,8 @@ asset={currentAsset} selected={assetInteraction.hasSelectedAsset(currentAsset.id)} selectionCandidate={assetInteraction.hasSelectionCandidate(currentAsset.id)} - thumbnailWidth={geometry.getWidth(i)} - thumbnailHeight={geometry.getHeight(i)} + thumbnailWidth={geometry.getWidth(index)} + thumbnailHeight={geometry.getHeight(index)} /> {#if showAssetName && !isTimelineAsset(asset)}
$t('context') }, { value: 'metadata', label: () => $t('filename') }, { value: 'description', label: () => $t('description') }, + { value: 'fullPath', label: () => $t('full_path_or_folder') }, { value: 'ocr', label: () => $t('ocr') }, ] as const; diff --git a/web/src/lib/components/shared-components/search-bar/SearchPeopleSection.svelte b/web/src/lib/components/shared-components/search-bar/SearchPeopleSection.svelte index 68e959325f..4b39866187 100644 --- a/web/src/lib/components/shared-components/search-bar/SearchPeopleSection.svelte +++ b/web/src/lib/components/shared-components/search-bar/SearchPeopleSection.svelte @@ -72,14 +72,14 @@ ? filterPeople(people, name) : filterPeople(people, name).slice(0, numberOfPeople)} -
+
{$t('people')}
{#each peopleList as person (person.id)} diff --git a/web/src/lib/components/shared-components/search-bar/SearchTextSection.svelte b/web/src/lib/components/shared-components/search-bar/SearchTextSection.svelte index 9e78531ac3..e63ab5bcf2 100644 --- a/web/src/lib/components/shared-components/search-bar/SearchTextSection.svelte +++ b/web/src/lib/components/shared-components/search-bar/SearchTextSection.svelte @@ -6,7 +6,7 @@ interface Props { query: string | undefined; - queryType?: 'smart' | 'metadata' | 'description' | 'ocr'; + queryType?: 'smart' | 'metadata' | 'description' | 'fullPath' | 'ocr'; } let { query = $bindable(), queryType = $bindable('smart') }: Props = $props(); @@ -33,6 +33,13 @@ bind:group={queryType} value="description" /> + {#if featureFlagsManager.value.ocr} {/if} @@ -51,6 +58,10 @@ + {:else if queryType === 'fullPath'} + + + {:else if queryType === 'ocr'} diff --git a/web/src/lib/components/shared-components/settings/SettingAccordion.svelte b/web/src/lib/components/shared-components/settings/SettingAccordion.svelte index d4b46b93c6..8d820047fb 100755 --- a/web/src/lib/components/shared-components/settings/SettingAccordion.svelte +++ b/web/src/lib/components/shared-components/settings/SettingAccordion.svelte @@ -1,10 +1,8 @@ @@ -72,7 +63,7 @@ -{/snippet} - - onClose()}> - -
- - {#if filters.length > 0 && (!type || type === 'filter')} -
- {#each filters as filter (filter.id)} - {@render stepButton(filter.title, filter.description, () => handleSelect('filter', filter))} - {/each} -
- {/if} - - - {#if actions.length > 0 && (!type || type === 'action')} -
- {#each actions as action (action.id)} - {@render stepButton(action.title, action.description, () => handleSelect('action', action))} - {/each} -
- {/if} -
-
-
diff --git a/web/src/lib/modals/AlbumOptionsModal.svelte b/web/src/lib/modals/AlbumOptionsModal.svelte index cc832453fc..93c95bbeaf 100644 --- a/web/src/lib/modals/AlbumOptionsModal.svelte +++ b/web/src/lib/modals/AlbumOptionsModal.svelte @@ -24,10 +24,11 @@ type Props = { album: AlbumResponseDto; + readOnly?: boolean; onClose: () => void; }; - let { album, onClose }: Props = $props(); + let { album, readOnly = false, onClose }: Props = $props(); const handleRoleSelect = async (user: UserResponseDto, role: AlbumUserRole | 'none') => { if (role === 'none') { @@ -72,14 +73,14 @@ onAlbumUpdate={(newAlbum) => (album = newAlbum)} /> - +
{$t('settings')}
{#if album.order} - +
-
- - {$t('shared_links')} - - + {#if !readOnly} +
+ + {$t('shared_links')} + + -
- - {#each sharedLinks as sharedLink (sharedLink.id)} - - {/each} - +
+ + {#each sharedLinks as sharedLink (sharedLink.id)} + + {/each} + +
-
+ {/if} diff --git a/web/src/lib/modals/AlbumPickerModal.svelte b/web/src/lib/modals/AlbumPickerModal.svelte index 34bf3c0490..3674a21acd 100644 --- a/web/src/lib/modals/AlbumPickerModal.svelte +++ b/web/src/lib/modals/AlbumPickerModal.svelte @@ -21,18 +21,15 @@ let search = $state(''); let selectedRowIndex: number = $state(-1); - interface Props { + type Props = { onClose: (albums?: AlbumResponseDto[]) => void; - } + }; let { onClose }: Props = $props(); onMount(async () => { - // TODO the server should *really* just return all albums (paginated ideally) - const ownedAlbums = await getAllAlbums({ shared: false }); - ownedAlbums.push.apply(ownedAlbums, await getAllAlbums({ shared: true })); - albums = ownedAlbums; - recentAlbums = albums.sort((a, b) => (new Date(a.updatedAt) > new Date(b.updatedAt) ? -1 : 1)).slice(0, 3); + albums = await getAllAlbums({}); + recentAlbums = [...albums].sort((a, b) => (new Date(a.updatedAt) > new Date(b.updatedAt) ? -1 : 1)).slice(0, 3); loading = false; }); @@ -175,7 +172,7 @@ bind:value={search} use:initInput /> -
+
{#each albumModalRows as row} {#if row.type === AlbumModalRowType.NEW_ALBUM} diff --git a/web/src/lib/modals/MapSettingsModal.svelte b/web/src/lib/modals/MapSettingsModal.svelte index 4b45def861..4dc0e72685 100644 --- a/web/src/lib/modals/MapSettingsModal.svelte +++ b/web/src/lib/modals/MapSettingsModal.svelte @@ -1,7 +1,6 @@ - {$t('birthdate_set_description')} -
- +
+ + + {$t('birthdate_set_description')} + {#if person.birthDate}
-
diff --git a/web/src/lib/modals/PluginMethodPicker.svelte b/web/src/lib/modals/PluginMethodPicker.svelte new file mode 100644 index 0000000000..230f5a2257 --- /dev/null +++ b/web/src/lib/modals/PluginMethodPicker.svelte @@ -0,0 +1,41 @@ + + + + {#await searchPluginMethods({ trigger })} +
+ +
+ {:then methods} + + {#each methods as method (method.key)} + onClose(method)}> +
+ {method.title} + {#if method.uiHints.includes('Filter')} + {$t('plugin_method_filter_type')} + {/if} + + {#if method.description} + {method.description} + {/if} +
+
+ {/each} +
+ {/await} +
diff --git a/web/src/lib/modals/SearchFilterModal.svelte b/web/src/lib/modals/SearchFilterModal.svelte index 0cb5276343..0145caacaf 100644 --- a/web/src/lib/modals/SearchFilterModal.svelte +++ b/web/src/lib/modals/SearchFilterModal.svelte @@ -54,6 +54,10 @@ query = searchQuery.originalFileName; } + if ('originalPath' in searchQuery && searchQuery.originalPath) { + query = searchQuery.originalPath; + } + return { query, ocr: searchQuery.ocr, @@ -133,6 +137,7 @@ ocr: filter.queryType === 'ocr' ? query : undefined, originalFileName: filter.queryType === 'metadata' ? query : undefined, description: filter.queryType === 'description' ? query : undefined, + originalPath: filter.queryType === 'fullPath' ? filter.query.trim() || undefined : undefined, country: filter.location.country, state: filter.location.state, city: filter.location.city, diff --git a/web/src/lib/modals/SlideshowSettingsModal.svelte b/web/src/lib/modals/SlideshowSettingsModal.svelte index b57d5c0584..6540f63124 100644 --- a/web/src/lib/modals/SlideshowSettingsModal.svelte +++ b/web/src/lib/modals/SlideshowSettingsModal.svelte @@ -11,7 +11,13 @@ } from '@mdi/js'; import { t } from 'svelte-i18n'; import SettingDropdown from '../components/shared-components/settings/SettingDropdown.svelte'; - import { SlideshowLook, SlideshowNavigation, SlideshowState, slideshowStore } from '../stores/slideshow.store'; + import { + SlideshowLook, + SlideshowMetadataOverlayMode, + SlideshowNavigation, + SlideshowState, + slideshowStore, + } from '../stores/slideshow.store'; const { slideshowDelay, @@ -22,6 +28,8 @@ slideshowAutoplay, slideshowRepeat, slideshowState, + slideshowShowMetadataOverlay, + slideshowMetadataOverlayMode, } = slideshowStore; type Props = { @@ -38,6 +46,8 @@ let tempSlideshowTransition = $state($slideshowTransition); let tempSlideshowAutoplay = $state($slideshowAutoplay); let tempSlideshowRepeat = $state($slideshowRepeat); + let tempSlideshowShowMetadataOverlay = $state($slideshowShowMetadataOverlay); + let tempSlideshowMetadataOverlayMode = $state($slideshowMetadataOverlayMode); const navigationOptions: Record = { [SlideshowNavigation.Shuffle]: { icon: mdiShuffle, title: $t('shuffle') }, @@ -51,7 +61,16 @@ [SlideshowLook.BlurredBackground]: { icon: mdiPanorama, title: $t('blurred_background') }, }; - const handleToggle = ( + const metadataOverlayModeOptions: Record = { + [SlideshowMetadataOverlayMode.DescriptionOnly]: { + title: $t('slideshow_metadata_overlay_mode_description_only'), + }, + [SlideshowMetadataOverlayMode.Full]: { + title: $t('slideshow_metadata_overlay_mode_full'), + }, + }; + + const handleToggle = ( record: RenderedOption, options: Record, ): undefined | Type => { @@ -71,6 +90,8 @@ $slideshowAutoplay = tempSlideshowAutoplay; $slideshowRepeat = tempSlideshowRepeat; $slideshowState = SlideshowState.PlaySlideshow; + $slideshowShowMetadataOverlay = tempSlideshowShowMetadataOverlay; + $slideshowMetadataOverlayMode = tempSlideshowMetadataOverlayMode; onClose(); }; @@ -111,6 +132,21 @@ + + + + + { + tempSlideshowMetadataOverlayMode = + handleToggle(option, metadataOverlayModeOptions) || tempSlideshowMetadataOverlayMode; + }} + /> + {$t('admin.slideshow_duration_description')} diff --git a/web/src/lib/modals/WorkflowAddStepModal.svelte b/web/src/lib/modals/WorkflowAddStepModal.svelte new file mode 100644 index 0000000000..d347a1feb3 --- /dev/null +++ b/web/src/lib/modals/WorkflowAddStepModal.svelte @@ -0,0 +1,75 @@ + + +{#if method} + +
+
+ {method.title} + {#if method.description} + {method.description} + {/if} +
+ +
+ + {#if method.schema} +
+
+ {$t('configuration')} + + + +
+ {/if} + + {#if debug} +
+ {$t('preview')} +